对所有实体使用全局查询过滤器

r3p*_*ica 3 c# entity-framework global-query-filter

我最近发现了全局过滤器,这很棒,因为我的任务是在我的应用程序中实现软删除。目前我已经这样做了:

// Query filters https://docs.microsoft.com/en-us/ef/core/querying/filters
modelBuilder.Entity<Address>().HasQueryFilter(m => !m.Deleted);
modelBuilder.Entity<Attribute>().HasQueryFilter(m => !m.Deleted);
modelBuilder.Entity<Brand>().HasQueryFilter(m => !m.Deleted);
modelBuilder.Entity<BrandAddress>().HasQueryFilter(m => !m.Deleted);
modelBuilder.Entity<BrandCategory>().HasQueryFilter(m => !m.Deleted);
modelBuilder.Entity<Category>().HasQueryFilter(m => !m.Deleted);
// many more entity types....
Run Code Online (Sandbox Code Playgroud)

所有实体都继承了一个BaseModel看起来像这样的:

public class BaseModel
{
    public Guid CreatedBy { get; set; }
    public Guid UpdatedBy { get; set; }
    public DateTime DateCreated { get; set; }
    public DateTime DateUpdated { get; set; }
    public bool Deleted { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

是否可以为继承BaseModel? 的任何类添加查询过滤器?就像是:

modelBuilder.Entity<BaseModel>().HasQueryFilter(m => !m.Deleted);
Run Code Online (Sandbox Code Playgroud)

所以我不会忘记(稍后)为我添加的模型添加查询过滤器?

abd*_*sco 8

您需要在运行时构造 lambda 表达式:

instance => !instance.IsDeleted
Run Code Online (Sandbox Code Playgroud)

现在,假设我们有这个接口,以及许多实现该接口的实体(直接或传递):

interface ISoftDelete
{
    bool IsDeleted { get; set; }
}

class Product : ISoftDelete
{
    public int Id { get; set; }
    public bool IsDeleted { get; set; }
    // ...
}
Run Code Online (Sandbox Code Playgroud)

我们希望将软删除查询过滤器应用于实现此接口的所有实体。

要查找在 DbContext 模型中注册的所有实体,我们可以使用IMutableModel.GetEntityTypes(). 然后我们过滤所有实现的实体ISoftDelete并添加设置自定义查询过滤器。

这是一个可以直接使用的扩展方法:

internal static class SoftDeleteModelBuilderExtensions
{
    public static ModelBuilder ApplySoftDeleteQueryFilter(this ModelBuilder modelBuilder)
    {
        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        {
            if (!typeof(ISoftDelete).IsAssignableFrom(entityType.ClrType))
            {
                continue;
            }

            var param = Expression.Parameter(entityType.ClrType, "entity");
            var prop = Expression.PropertyOrField(param, nameof(ISoftDelete.IsDeleted));
            var entityNotDeleted = Expression.Lambda(Expression.Equal(prop, Expression.Constant(false)), param);

            entityType.SetQueryFilter(entityNotDeleted);
        }

        return modelBuilder;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,我们可以在 DbContext 中使用它:

class AppDbContext : DbContext
{
    // ...

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>();
        modelBuilder.ApplySoftDeleteQueryFilter(); // <-- must come after all entity definitions
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @tiakham,这应该在“OnModelCreating”之外完成,因为租户通常会根据请求进行更改,因此在请求范围的上下文中应用该过滤器是有意义的。拥有通用扩展方法可以帮助“dbContext.Products.ForTenant(id)...” (2认同)

Gur*_*ron 7

对于最新的 EF Core 版本(也应该适用于 3.0,对于早期版本,表达式替换应该手动处理,请参阅ReplacingExpressionVisitor调用),您可以使用一些反射(最少的)、表达式树IMutableModel.GetEntityTypes您的OnModelCreating方法来自动化它。这样的事情应该工作:

// define your filter expression tree
Expression<Func<BaseModel, bool>> filterExpr = bm => !bm.Deleted;
foreach (var mutableEntityType in modelBuilder.Model.GetEntityTypes())
{
    // check if current entity type is child of BaseModel
    if (mutableEntityType.ClrType.IsAssignableTo(typeof(BaseModel)))
    {
        // modify expression to handle correct child type
        var parameter = Expression.Parameter(mutableEntityType.ClrType);
        var body = ReplacingExpressionVisitor.Replace(filterExpr.Parameters.First(), parameter, filterExpr.Body);
        var lambdaExpression = Expression.Lambda(body, parameter);

        // set filter
        mutableEntityType.SetQueryFilter(lambdaExpression);
    }
}
Run Code Online (Sandbox Code Playgroud)