efcore
efcore copied to clipboard
HasQueryFilter works only in LINQ-2-SQL
Hi all,
According to "HasQueryFilter docs", each query should have been applied with predicate, passed to HasQueryFilter model builder method. Apparently, it only works when linq-to-sql is performed or we perform that operation on entity without any relations. Otherwise, when entity is still tracked, HasQueryFilter does not work.
Expected result: DbContext ensures data consistency between local context and remote db.
Sample code. "1-n relation, querying parent with included children, HasQueryFilter does not apply on children":
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
internal class Program
{
private static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/test", () =>
{
const int entityId = 1;
// 1. Adding entity
using var ctx1 = GetContext();
var parent = new SomeEntity
{
Id = entityId,
Data = "Hello world",
IsDeleted = false,
};
ctx1.Add(parent);
ctx1.Add(new SomeChildEntity
{
Id = 2,
Data = "I am a child",
Parent = parent,
});
ctx1.SaveChanges();
// 2. Getting same entity from fresh new context and deleting it
using var ctx2 = GetContext();
var entity = ctx2.SomeEntities.Include(x => x.Children).FirstOrDefault(x => x.Id == 1);
entity.Children.First().IsDeleted = true;
ctx2.SaveChanges();
// Entity is still in ctx2, so I try to query it, having in mind that HasQueryFilter has been applied.
var entityThatShouldBeNull = ctx2.SomeEntities
.Include(x => x.Children)
.FirstOrDefault(x => x.Id == 1)
.Children
.FirstOrDefault(); // but it is not null
// 3. Let's get same entity from new context
using var ctx3 = GetContext();
var entityThatShouldBeNull2 = ctx3.SomeEntities
.Include(x => x.Children)
.FirstOrDefault(x => x.Id == 1)
.Children
.FirstOrDefault(); // is null
return Results.Accepted();
});
app.Run();
}
private static InMemContext GetContext()
{
var _contextOptions = new DbContextOptionsBuilder<InMemContext>()
.UseInMemoryDatabase("BloggingControllerTest")
.ConfigureWarnings(b => b.Ignore(InMemoryEventId.TransactionIgnoredWarning))
.Options;
return new InMemContext(_contextOptions);
}
}
internal class InMemContext : DbContext
{
public InMemContext(DbContextOptions options) : base(options)
{
}
public virtual DbSet<SomeEntity> SomeEntities { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<SomeEntity>().HasKey(x => x.Id);
modelBuilder.Entity<SomeEntity>().HasQueryFilter(x => !x.IsDeleted);
modelBuilder.Entity<SomeEntity>().HasMany(x => x.Children).WithOne(x => x.Parent);
modelBuilder.Entity<SomeChildEntity>().HasQueryFilter(x => !x.IsDeleted);
}
}
internal class SomeEntity
{
public int Id { get; set; }
public string Data { get; set; }
public bool IsDeleted { get; set; }
public ICollection<SomeChildEntity> Children { get; set; } = new List<SomeChildEntity>();
}
internal class SomeChildEntity
{
public int Id { get; set; }
public string Data { get; set; }
public bool IsDeleted { get; set; }
public SomeEntity Parent { get; set; }
}
Include provider and version information
EF Core version: 5.0.8 Database provider: Microsoft.EntityFrameworkCore.SqlServer & Microsoft.EntityFrameworkCore.InMemory Target framework: .NET 6.0 Operating system: Windows 10 Enterprise 21H2 IDE: Visual Studio 2022
As the name indicates, query filters are for filtering queries. It would be technically very difficult to keep the context in sync due to the semantic differences between store queries and anything possible on tracked entities. That aside, I don't think we would do this anyway, since it is useful to execute multiple queries inside a single unit-of-work and have the tracked results be the aggregate of these results.
Often problems like this come from using a long-lived DbContext instance, rather than using a new context per unit-of-work. Additionally, in some situations, it may be appropriate to use no-tracking queries so that the results of each remain independent.