EF Core: Soft delete with shadow properties and query filters

此生再无相见时 提交于 2019-12-17 16:13:43

问题


I've created an interface to try to do a soft delete, mixing shadow properties and query filters. But it's not working.

public interface IDeletableEntity {}

And then in my model builder

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

But the line with the query filter doesn't compile. The error I got is "cannot convert lambda expression to type 'lambda expression' because it is not a delegate type"

If I do this it's working.

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

there are any way to do this? It's in order to have an Interface with IDeletableEntity and not have to do it, in every Entity that I want to use a soft delete Entity

Many thanks in advance,


回答1:


HasQueryFilter of the non generic EntityTypeBuilder (as opposed to the generic EntityTypeBuilder<TEntity>) is almost unusable because there is no easy way to create the expected LambdaExpression.

One solution is to build the lambda expression by hand using the Expression class methods:

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

Another one is to use a prototype expression and use a parameter replacer to bind the parameter with actual type:

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

where ReplaceParameter is one of the custom helper extension method I'm using for expression tree manipulation:

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

But most natural solution in my opinion is to move the configuration code in a generic method and call it via reflection. For instance:

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

and then

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



回答2:


I've found a simple solution for my answer ;-). Thanks anyway Ivan Stoev

The interface is:

public interface IDeletableEntity
{
    bool IsDeleted { get; }
}

And in your model Builder configuration:

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

You need to convertfilterExpression

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



回答3:


A small enhancement to @SamazoOo's answer. You can write an extension method to make it more consistent.

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


来源:https://stackoverflow.com/questions/47673524/ef-core-soft-delete-with-shadow-properties-and-query-filters

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!