If I use a configuration class implementing IEntityTypeConfiguration<TEntity> to set a query filter, and pass a delegate (not an Expression!) to this class that is used in the expression passed to HasQueryFilter, the delegate is only called once. I found a workaround (see below), but I want to understand why this issue happens. Can someone explain why this is?
The problem is, that the method for the delegate uses a dynamic condition. Because the delegate is only called for the very first request, the query filter will not change even if the condition changes.
Here is an example (see also the complete test solution):
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
private readonly Func<int> getMinProductId;
private static int minProductId = 0;
public ProductConfiguration(Func<int> getMinProductId)
{
this.getMinProductId = getMinProductId;
}
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.HasQueryFilter(e => e.Id >= getMinProductId());
}
}
public sealed class DatabaseContext: DbContext
{
private static int minProductId = 0;
public DbSet<Product> Products { get; set; }
public DatabaseContext()
{
this.Database.Migrate();
}
public DatabaseContext(DbContextOptions<DatabaseContext> options): base(options)
{
this.Database.Migrate();
}
protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
builder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=asp-net-ef-test;Trusted_Connection=true;" +
"MultipleActiveResultSets=True");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Does not work!
modelBuilder.ApplyConfiguration(new ProductConfiguration(GetMinProductId));
base.OnModelCreating(modelBuilder);
}
private int GetMinProductId()
{
minProductId++;
return minProductId;
}
}
This example should return Product entities with a minimal ID that is increased for every call. But it does not. The products stay the same every time they are read.
I also tried to put GetMinProductId in DatabaseContext and pass a delegate to the ProductConfiguration with the same result.
The only solution was, to put GetMinProductId in DatabaseContext, and to pass an Expression to ProductConfiguration:
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
private readonly Expression<Func<Product, bool>> expression;
private static int minProductId = 0;
public ProductConfiguration(Expression<Func<Product, bool>> expression)
{
this.expression = expression;
}
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.HasQueryFilter(expression);
}
}
public sealed class DatabaseContext: DbContext
{
private static int minProductId = 0;
public DbSet<Product> Products { get; set; }
public DatabaseContext()
{
this.Database.Migrate();
}
public DatabaseContext(DbContextOptions<DatabaseContext> options): base(options)
{
this.Database.Migrate();
}
protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
builder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=net-ef-query-filter-test;Trusted_Connection=true;" +
"MultipleActiveResultSets=True");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Works!
modelBuilder.ApplyConfiguration(new ProductConfiguration(e => e.Id > GetMinProductId()));
base.OnModelCreating(modelBuilder);
}
private int GetMinProductId()
{
minProductId++;
return minProductId;
}
}
With this solution the list of products get reduced every time they are read, as expected.
Just to clarify: I am not looking for the approach to define the query filter directly in OnConfiguring (which works). I am looking for the reason why approaches with a configuration class that not get an Expression passed do not work. Also I would appreciate a solution that uses a delegate.