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

问题描述 投票:0回答:4

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

// Query filters https://learn.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....

所有实体都继承一个

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; }
}

是否可以为任何继承

BaseModel
的类添加查询过滤器? 比如:

modelBuilder.Entity<BaseModel>().HasQueryFilter(m => !m.Deleted);

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

c# entity-framework entity-framework-core global-query-filter
4个回答
31
投票

对于最新的 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);
    }
}

您也可以将其转移到基于Conventions的方法(通过

IModelFinalizingConvention
)。


8
投票

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

instance => !instance.IsDeleted

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

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

class Product : ISoftDelete
{
    public int Id { get; set; }
    public bool IsDeleted { get; set; }
    // ...
}

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

要查找在 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;
    }
}

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

class AppDbContext : DbContext
{
    // ...

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

3
投票

这是

ApplyQueryFilter
的通用实现。它接受任何基本实体并对它们应用过滤器。它还保留以前应用的过滤器,以便可以将它们组合起来。

没有魔法字符串,只有静态类型的表达式:

modelBuilder.ApplyQueryFilter<BaseModel>(e => !e.IsDeleted);

// or if soft delete is controlled by interface
modelBuilder.ApplyQueryFilter<ISoftDelete>(e => !e.IsDeleted);

及实施:

public static class ModelBuilderExtensions
{
    public static void ApplyQueryFilter<TBaseEntity>(this ModelBuilder builder,
        Expression<Func<TBaseEntity, bool>> filter)
    {
        var acceptableItems = builder.Model.GetEntityTypes()
            .Where(et => typeof(TBaseEntity).IsAssignableFrom(et.ClrType))
            .ToList();

        foreach (var entityType in acceptableItems)
        {
            var entityParam = Expression.Parameter(entityType.ClrType, "e");

            // replacing parameter with actual type
            var filterBody = ReplacingExpressionVisitor.Replace(filter.Parameters[0], entityParam, filter.Body);

            var filterLambda = entityType.GetQueryFilter();
            // Other filter already present, combine them
            if (filterLambda != null)
            {
                filterBody = ReplacingExpressionVisitor.Replace(entityParam, filterLambda.Parameters[0], filterBody);
                filterBody = Expression.AndAlso(filterLambda.Body, filterBody);
                filterLambda = Expression.Lambda(filterBody, filterLambda.Parameters);
            }
            else
            {
                filterLambda = Expression.Lambda(filterBody, entityParam);
            }               

            entityType.SetQueryFilter(filterLambda);
        }
    }
}

0
投票

使用下面的代码获取所有实体并过滤属性:

foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        {
            if (entityType.ClrType.GetCustomAttributes(typeof(AuditableAttribute), true).Length > 0)
            {                                       
                modelBuilder.Entity(entityType.Name).Property<bool>("IsRemoved");                   
            }

            var isActiveProperty = entityType.FindProperty("IsRemoved");
            if (isActiveProperty != null && isActiveProperty.ClrType == typeof(bool))
            {
                var entityBuilder = modelBuilder.Entity(entityType.ClrType);
                var parameter = Expression.Parameter(entityType.ClrType, "e");
                var methodInfo = typeof(EF).GetMethod(nameof(EF.Property))!.MakeGenericMethod(typeof(bool))!;
                var efPropertyCall = Expression.Call(null, methodInfo, parameter, Expression.Constant("IsRemoved"));
                var body = Expression.MakeBinary(ExpressionType.Equal, efPropertyCall, Expression.Constant(false));
                var expression = Expression.Lambda(body, parameter);
                entityBuilder.HasQueryFilter(expression);
            }                
        }  
© www.soinside.com 2019 - 2024. All rights reserved.