EF Core: обратимое удаление с теневыми свойствами и фильтрами запросов

Я создал интерфейс, чтобы попытаться выполнить обратимое удаление, смешивая теневые свойства и фильтры запросов. Но это не работает.

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

Но строка с фильтром запросов не компилируется. Я получил ошибку: «Невозможно преобразовать лямбда-выражение в тип« лямбда-выражение », потому что это не тип делегата»

Если я это сделаю, это сработает.

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

есть ли способ сделать это? Это для того, чтобы иметь интерфейс с IDeletableEntity и не делать этого в каждой сущности, которую я хочу использовать для мягкого удаления.

Спасибо заранее,


person SamazoOo    schedule 06.12.2017    source источник


Ответы (5)


HasQueryFilter неуниверсального EntityTypeBuilder (в отличие от универсального EntityTypeBuilder<TEntity>) почти непригоден, потому что нет простого способа создать ожидаемый LambdaExpression.

Одним из решений является создание лямбда-выражения вручную с использованием методов класса Expression:

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

Другой - использовать выражение прототипа

Expression<Func<object, bool>> filter = 
    e => EF.Property<bool>(e, "IsDeleted") == false;

и используйте заменитель параметра, чтобы связать параметр с фактическим типом:

.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 })
);
person Ivan Stoev    schedule 06.12.2017
comment
Спасибо, вы избавили меня от многих проблем здесь - person johnny 5; 30.12.2017
comment
что такое filter во втором блоке - person deadManN; 10.03.2020
comment
@deadManN Хороший вопрос, похоже, я забыл его включить. Спасибо за указание на это, сообщение обновлено. - person Ivan Stoev; 10.03.2020
comment
благодаря вам, это меня долго беспокоило (я имею в виду весь ответ), но до сих пор не было способа скомпилировать и использовать Expression<Func<...>>(x=>x.IsDeleted) напрямую или как-то близко - person deadManN; 10.03.2020

Я нашел простое решение для своего ответа ;-). Все равно спасибо Ивану Стоеву

Интерфейс:

public interface IDeletableEntity
{
    bool IsDeleted { get; }
}

И в вашей конфигурации Model 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);
                }
person SamazoOo    schedule 12.02.2018

Небольшое улучшение ответа @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);
    }
person Chamika Goonetilaka    schedule 08.03.2019

Это не работает для меня, .net core 3.1, поэтому я попробовал следующий подход, который чем-то похож:

// fetch entity types by reflection then: 

 softDeletedEntityTypes.ForEach(entityType =>
            {
                modelBuilder.Entity(entityType, builder =>
                {
                    builder.Property<bool>("IsDeleted");
                    builder.HasQueryFilter(GenerateQueryFilterExpression(entityType));
                });
            });


 private static LambdaExpression GenerateQueryFilterExpression(Type entityType)
        {            
             // the following lambda expression should be generated
             // e => !EF.Property<bool>(e, "IsDeleted")); 

            var parameter = Expression.Parameter(entityType, "e"); // e =>

            var fieldName = Expression.Constant("IsDeleted", typeof(string)); // "IsDeleted"

            // EF.Property<bool>(e, "IsDeleted")
            var genericMethodCall = Expression.Call(typeof(EF), "Property", new[] {typeof(bool)}, parameter, fieldName);

            // !EF.Property<bool>(e, "IsDeleted"))
            var not = Expression.Not(genericMethodCall);

            // e => !EF.Property<bool>(e, "IsDeleted"));
            var lambda = Expression.Lambda(not, parameter);
        }

person Saeed Ganji    schedule 18.06.2020

То, что я сделал, было

builder.Model.GetEntityTypes()
           .Where(p => typeof(IDeletableEntity).IsAssignableFrom(p.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);
    }
person SamazoOo    schedule 19.06.2020