Say I have the following interface that can be implemented on any entity that I want to support soft-deletion.
``` c#
public interface ISoftDeletableEntity
{
bool IsDeleted { get; set; }
}
It would be nice to be able to define a global query filter for any entity type that implements this interface like this:
``` c#
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ISoftDeletableEntity>().HasQueryFilter(e => e.IsDeleted == false);
}
Currently this throws the following exception: System.ArgumentException: 'The entity type 'MyApp.ISoftDeletableEntity' provided for the argument 'clrType' must be a reference type.'
@nphmuller This is something that can be done with bulk configuration on the mapped entity types:
```C#
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes()
.Where(e => typeof(ISoftDeletableEntity).IsAssignableFrom(e.ClrType)))
{
modelBuilder
.Entity(entityType.ClrType)
.HasQueryFilter(ConvertFilterExpression
}
}
private static LambdaExpression ConvertFilterExpression
Expression
Type entityType)
{
var newParam = Expression.Parameter(entityType);
var newBody = ReplacingExpressionVisitor.Replace(filterExpression.Parameters.Single(), newParam, filterExpression.Body);
return Expression.Lambda(newBody, newParam);
}
Making a better experience around this is covered by #9117 and #6787.
The code is more complicated in this case than in many others because it requires constructing a lambda expression where the parameter is validated to be the exact entity type--not the interface. I'm going to re-purpose this bug to automatically handle base types/interfaces in EF code. After this issue is fixed it should be possible to simplify the above to:
```C#
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
Expression<Func<ISoftDeletableEntity, bool>> expression = e => !e.IsDeleted;
foreach (var entityType in modelBuilder.Model.GetEntityTypes()
.Where(e => typeof(ISoftDeletableEntity).IsAssignableFrom(e.ClrType)))
{
modelBuilder.Entity(entityType.ClrType).HasQueryFilter(expression);
}
}
@ajcvickers Wouldn't be more efficient to set IMutableEntityType.QueryFilter like my example at https://github.com/aspnet/EntityFrameworkCore/issues/8881#issuecomment-337743060? That way you don't look up every affected entity type twice. Please "Automatically perform required conversions when query filter is defined" in that manner, too.
@ajcvickers
Nice. Was hoping it was possible already, but got stuck rewriting the expression. Thanks for the sample. It helps out a lot!
Totally agree with the repurpose. The sample after the fix is way easier to write.
I've set up a Gist which shows my query filter use-case and the code I had to write to get it working.
https://gist.github.com/nphmuller/8891c315d79aaaf720f9164cd0f10400~~
https://gist.github.com/nphmuller/05ff66dfa67e1d02cdefcd785661a34d
Most helpful comment
@nphmuller This is something that can be done with bulk configuration on the mapped entity types:
```C#(e => !e.IsDeleted, entityType.ClrType));
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes()
.Where(e => typeof(ISoftDeletableEntity).IsAssignableFrom(e.ClrType)))
{
modelBuilder
.Entity(entityType.ClrType)
.HasQueryFilter(ConvertFilterExpression
}
}
private static LambdaExpression ConvertFilterExpression(> filterExpression,
Expression
Type entityType)
{
var newParam = Expression.Parameter(entityType);
var newBody = ReplacingExpressionVisitor.Replace(filterExpression.Parameters.Single(), newParam, filterExpression.Body);
}