EF Core: soft removal using shadow properties and query filters

I created an interface to try to do a soft delete, mix shadow properties and query filters. But it does not work.

public interface IDeletableEntity {} 

And then in my model constructor

  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 query filter string does not compile. The error I received is "cannot convert lambda expression to input lambda expression because it is not a delegate type"

If I do, it works.

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

Is there any way to do this? This is in order to have an interface with IDeletableEntity and you do not need to do this, in every Entity that I want to use for soft removal of Entity

Thank you very much in advance,

+4
source share
2 answers

HasQueryFilter not a generic EntityTypeBuilder (unlike a 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 manually using the methods of the Expression class:

 .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 is to use a prototype expression and use a replacement parameter to bind the parameter to the 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 helper helper extension methods that I use to process the expression tree:

 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 the most natural solution, in my opinion, is to transfer the configuration code to a general method and call it through 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 }) ); 
+5
source

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

Interface:

 public interface IDeletableEntity { bool IsDeleted { get; } } 

And in the configuration of the Builder model:

 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 convert the expression

 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); } 
+1
source

Source: https://habr.com/ru/post/1269822/


All Articles