EF Core:使用阴影属性和查询过滤器进行软删除

问题描述 投票:8回答:3

我创建了一个界面来尝试进行软删除,混合阴影属性和查询过滤器。但它不起作用。

public interface IDeletableEntity {}

然后在我的模型构建器中

 builder.Model.GetEntityTypes()
                .Where(entityType => typeof(IDeletableEntity).IsAssignableFrom(entityType.ClrType))
                .ToList()
                .ForEach(entityType =>
                {
                    builder.Entity(entityType.ClrType).Property<Boolean>("IsDeleted");
                    builder.Entity(entityType.ClrType).HasQueryFilter(e => EF.Property<Boolean>(e, "IsDeleted") == false);
                });

但是查询过滤器的行不能编译。我得到的错误是“无法将lambda表达式转换为'lambda表达式',因为它不是委托类型”

如果我这样做它就会起作用。

builder.Entity<MyEntity>().HasQueryFilter(m => EF.Property<Boolean>(m, "IsDeleted") == false);

有什么方法可以做到这一点?这是为了在每个我想使用软删除实体的实体中都有一个IDeletableEntity接口而不必这样做

提前谢谢了,

c# entity-framework ef-core-2.0
3个回答
12
投票

非通用HasQueryFilterEntityTypeBuilder(与通用的EntityTypeBuilder<TEntity>相对)几乎无法使用,因为没有简单的方法来创建预期的LambdaExpression

一种解决方案是使用Expression类方法手动构建lambda表达式:

.ForEach(entityType =>
{
    builder.Entity(entityType.ClrType).Property<Boolean>("IsDeleted");
    var parameter = Expression.Parameter(entityType.ClrType, "e");
    var body = Expression.Equal(
        Expression.Call(typeof(EF), nameof(EF.Property), new[] { typeof(bool) }, parameter, Expression.Constant("IsDeleted")),
    Expression.Constant(false));
    builder.Entity(entityType.ClrType).HasQueryFilter(Expression.Lambda(body, parameter));
});

另一种方法是使用原型表达式并使用参数替换器将参数与实际类型绑定:

.ForEach(entityType =>
{
    builder.Entity(entityType.ClrType).Property<Boolean>("IsDeleted");
    var parameter = Expression.Parameter(entityType.ClrType, "e");
    var body = filter.Body.ReplaceParameter(filter.Parameters[0], parameter);
    builder.Entity(entityType.ClrType).HasQueryFilter(Expression.Lambda(body, parameter));
});

其中ReplaceParameter是我用于表达式树操作的自定义助手扩展方法之一:

public static partial class ExpressionUtils
{
    public static Expression ReplaceParameter(this Expression expr, ParameterExpression source, Expression target) =>
        new ParameterReplacer { Source = source, Target = target }.Visit(expr);

    class ParameterReplacer : System.Linq.Expressions.ExpressionVisitor
    {
        public ParameterExpression Source;
        public Expression Target;
        protected override Expression VisitParameter(ParameterExpression node) => node == Source ? Target : node;
    }
}

但在我看来,最自然的解决方案是在通用方法中移动配置代码并通过反射调用它。例如:

static void ConfigureSoftDelete<T>(ModelBuilder builder)
    where T : class, IDeletableEntity
{
    builder.Entity<T>().Property<Boolean>("IsDeleted");
    builder.Entity<T>().HasQueryFilter(e => EF.Property<bool>(e, "IsDeleted") == false);
}

然后

.ForEach(entityType => GetType()
    .GetMethod(nameof(ConfigureSoftDelete), BindingFlags.NonPublic | BindingFlags.Static)
    .MakeGenericMethod(entityType.ClrType)
    .Invoke(null, new object[] { builder })
);

9
投票

我为我的答案找到了一个简单的解决方案;-)。非常感谢Ivan Stoev

界面是:

public interface IDeletableEntity
{
    bool IsDeleted { get; }
}

在您的模型Builder配置中:

builder.Model.GetEntityTypes()
                       .Where(entityType => typeof(IDeletableEntity).IsAssignableFrom(entityType.ClrType))
                       .ToList()
                       .ForEach(entityType =>
                       {
                           builder.Entity(entityType.ClrType)
                           .HasQueryFilter(ConvertFilterExpression<IDeletableEntity>(e => !e.IsDeleted, entityType.ClrType));
                       });

您需要转换过滤器表达式

private static LambdaExpression ConvertFilterExpression<TInterface>(
                            Expression<Func<TInterface, bool>> filterExpression,
                            Type entityType)
                {
                    var newParam = Expression.Parameter(entityType);
                    var newBody = ReplacingExpressionVisitor.Replace(filterExpression.Parameters.Single(), newParam, filterExpression.Body);

                    return Expression.Lambda(newBody, newParam);
                }

2
投票

@ SamazoOo答案的一个小改进。您可以编写扩展方法以使其更加一致。

public static EntityTypeBuilder HasQueryFilter<T>(this EntityTypeBuilder entityTypeBuilder, Expression<Func<T, bool>> filterExpression)
    {
        var param = Expression.Parameter(entityTypeBuilder.Metadata.ClrType);
        var body = ReplacingExpressionVisitor.Replace(filterExpression.Parameters.Single(), param, filterExpression.Body);

        var lambdaExp = Expression.Lambda(body, param);

        return entityTypeBuilder.HasQueryFilter(lambdaExp);
    }
© www.soinside.com 2019 - 2024. All rights reserved.