Комплексное редактирование тела Expression> - PullRequest
1 голос
/ 29 октября 2019

Резюме: Я хочу знать, как я могу определить конкретные определения из тела выражения и затем изменить его так, как я хочу, например,

e.Entity.ListA.Union(e.ListB).Any(...)...

На

e.Entity != null && 
((e.Entity.ListA != null && e.Entity.ListA.Any(...)) 
|| (e.Entity.ListB != null && e.Entity.ListB.Any(...)))

Только использование техник Linq Expression, как я вижу, является идеальным решением

В рамках написания чистого кода на C # я написал набор предопределенных выражений и с помощью расширений LinqKit, которые я могу комбинировать междуих, следовательно, это расширит динамизм написания сложных выражений легко, пока все не будет в порядке. Кроме того, я хочу использовать их для фильтрации случаев IQuerable и IEnumerable. Однако, как вы знаете, в некоторых случаях определенное выражение не будет работать ни в Former, ни в последнем, я успешно избежал многих таких проблем. Пока я не дошел до случая, когда я принял решение, но я все еще чувствую, что это не идеал.

Сначала я начну с показа проблемы, затем объясню желаемое решение, в конце я поделюсь своимпопытка.

//---
public class AssignmentsEx : BaseEx
{ 


//.........

/// <summary>
/// (e.FreeRoles AND e.RoleClass.Roles) ⊆ ass.AllRoles
/// </summary>
public static Expression<Func<T, bool>> RolesInclosedBy<T>(IAssignedInstitution assignedInstitution) where T : class, IAssignedInstitution
    {
        var allStaticRoles = AppRolesStaticData.AdminRolesStr.GetAll();
        var assAllRoles = assignedInstitution.AllRoles.Select(s => s.Name).ToList();
        var hasAllRoles = allStaticRoles.All(assR => assAllRoles.Any(sR => sR == assR));

        if (hasAllRoles)
            return e => true;

// for LINQ to SQL the expression works perfectly as you know 
// the expression will be translated to an SQL code
// for IEnumerable case the nested object Roles with throw null obj ref 
// exception if the RoleClass is null (and this is a healthy case from code execution
// 
       return Expression<Func<T, bool>> whenToEntity = e => e.FreeRoles.Union(e.RoleClass.Roles).All(eR => assAllRoles.Any(assR => assR == eR.Name));
    }

//.........

}

Как видите, если я использую этот метод, чтобы определить список объектов с RoleClass равен нулю или FreeRoles равен нулю, то будет выброшено исключение NullException.

- лучшее -ожидаемое предположение Я думаю, что это будет играть на три фактора:

  • возможность обнаружить нужный фрагмент из тела выражения

  • изменить фрагмент, чтобы бытьпо мере необходимости для случая IEnumerable или наоборот

  • реконструировать и вернуть новое выражение

таким образом, я буду поддерживать метод статичным и изменятьэто с помощью метода расширения: например, ex.WithSplittedUnion ()

, а не традиционным способом, т.е. я использую сейчас, как следует

public class AssignmentsEx
{

public LinqExpressionPurpose purpose{get;}

public AssignmentsEx(LinqExpressionPurpose purpose) : base(purpose)
    {
          Purpose = purpose
    }

 public Expression<Func<T, bool>> RolesInclosedBy<T>(IAssignedInstitution assignedInstitution) where T : class, IAssignedInstitution
    {
        var allStaticRoles = AppRolesStaticData.AdminRolesStr.GetAll();
        var assAllRoles = assignedInstitution.AllRoles.Select(s => s.Name).ToList();
        var hasAllRoles = allStaticRoles.All(assR => assAllRoles.Any(sR => sR == assR));

        if (hasAllRoles)
            return e => true;

        Expression<Func<T, bool>> whenToObject = e => (e.FreeRoles == null || e.FreeRoles.All(eR => assAllRoles.Any(assR => assR == eR.Name)))
        && (e.RoleClass == null || e.RoleClass.Roles == null || e.RoleClass.Roles.All(eR => assAllRoles.Any(assR => assR == eR.Name)));

        Expression<Func<T, bool>> whenToEntity = e => e.FreeRoles.Union(e.RoleClass.Roles).All(eR => assAllRoles.Any(assR => assR == eR.Name));

        return Purpose switch
        {
            LinqExpressionPurpose.ToEntity => whenToEntity,
            LinqExpressionPurpose.ToObject => whenToObject,
            _ => null,
        };
    }
}

Я надеюсь, что объяснение понятно, заранее спасибо

1 Ответ

2 голосов
/ 29 октября 2019

Насколько я понимаю, вам нужно ExpressionVisitor пройти и изменить ExpressionTree. Одна вещь, которую я бы изменил, это то, как вы звоните Any. Вместо

e.Entity != null && 
((e.Entity.ListA != null && e.Entity.ListA.Any(...)) 
|| (e.Entity.ListB != null && e.Entity.ListB.Any(...)))

я бы выбрал

(
    e.Entity != null && e.Entity.ListA != null && e.Entity.ListB != null
        ? e.Entity.ListA.Union(e.Entity.ListB)
        : e.Entity != null && e.Entity.ListA != null
            ? e.Entity.ListA
            : e.Entity.ListB != null
                ? e.Entity.ListB
                : new Entity[0]
).Any(...)

Мне проще построить ExpressionTree, и результат будет таким же.

Пример кода:

public class OptionalCallFix : ExpressionVisitor
{
    private readonly List<Expression> _conditionalExpressions = new List<Expression>();
    private readonly Type _contextType;
    private readonly Type _entityType;

    private OptionalCallFix(Type contextType, Type entityType)
    {
        this._contextType = contextType;
        this._entityType = entityType;
    }

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        // Replace Queryable.Union(left, right) call with:
        //     left == null && right == null ? new Entity[0] : (left == null ? right : (right == null ? left : Queryable.Union(left, right)))
        if (node.Method.DeclaringType == typeof(Queryable) && node.Method.Name == nameof(Queryable.Union))
        {
            Expression left = this.Visit(node.Arguments[0]);
            Expression right = this.Visit(node.Arguments[1]);

            // left == null
            Expression leftIsNull = Expression.Equal(left, Expression.Constant(null, left.Type));

            // right == null
            Expression rightIsNull = Expression.Equal(right, Expression.Constant(null, right.Type));

            // new Entity[0].AsQueryable()
            Expression emptyArray = Expression.Call
            (
                typeof(Queryable),
                nameof(Queryable.AsQueryable),
                new [] { this._entityType },
                Expression.NewArrayInit(this._entityType, new Expression[0])
            );

            // left == null && right == null ? new Entity[0] : (left == null ? right : (right == null ? left : Queryable.Union(left, right)))
            return Expression.Condition
            (
                Expression.AndAlso(leftIsNull, rightIsNull),
                emptyArray,
                Expression.Condition
                (
                    leftIsNull,
                    right,
                    Expression.Condition
                    (
                        rightIsNull,
                        left,
                        Expression.Call
                        (
                            typeof(Queryable), 
                            nameof(Queryable.Union), 
                            new [] { this._entityType }, 
                            left, 
                            Expression.Convert(right, typeof(IEnumerable<>).MakeGenericType(this._entityType))
                        )
                    )
                )
            );
        }

        return base.VisitMethodCall(node);
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        Expression expression = this.Visit(node.Expression);

        // Check if expression should be fixed
        if (this._conditionalExpressions.Contains(expression))
        {
            // replace e.XXX with e == null ? null : e.XXX
            ConditionalExpression condition = Expression.Condition
            (
                Expression.Equal(expression, Expression.Constant(null, expression.Type)),
                Expression.Constant(null, node.Type),
                Expression.MakeMemberAccess(expression, node.Member)
            );

            // Add fixed expression to the _conditionalExpressions list
            this._conditionalExpressions.Add(condition);

            return condition;
        }

        return base.VisitMember(node);
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node.Type == this._contextType)
        {
            // Add ParameterExpression to the _conditionalExpressions list
            // It is used in VisitMember method to check if expression should be fixed this way
            this._conditionalExpressions.Add(node);
        }

        return base.VisitParameter(node);
    }

    public static IQueryable<TEntity> Fix<TContext, TEntity>(TContext context, in Expression<Func<TContext, IQueryable<TEntity>>> method)
    {
        return ((Expression<Func<TContext, IQueryable<TEntity>>>)new OptionalCallFix(typeof(TContext), typeof(TEntity)).Visit(method)).Compile().Invoke(context);
    }
}

Вы можете назвать это так:

OptionalCallFix.Fix(context, ctx => ctx.Entity.ListA.Union(ctx.ListB));
...