Complex edit of a body Expression>

c# entity-framework entity-framework-core expression-trees linq

Question

Summary: I'm interested in learning how to extract certain definitions from an expression's body and then modify it as I like, for example,

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

To

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

The best approach, in my opinion, is to just use Linq Expression methods.

In order to write clean C# code, I created a collection of preset expressions that I can combine using LinqKit extensions. This will increase the dynamism of creating complicated expressions quickly, as long as everything is in order. Furthermore, I wish to use them to filter IEnumerable and IQuerable scenarios. As you are aware, there are various situations in which the specified expression will not function in the former or the latter. I have effectively avoided many of these issues. Up to the point when I came up with a solution that I still believe is not optimal.

I'll begin by outlining the issue, follow that with an explanation of the desired resolution, and then present my own effort.

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

//.........

}

As you can see, if I use this method to construct a list of objects where either the RoleClass or the FreeRoles parameter is null, a NullException will be raised.

— the most promising recommendation It will, in my opinion, depend on three things:

  • the potential to extract the required piece from the expression body

  • alter the fragment as necessary for the IEnumerable case or the opposite

  • reassemble and output a new expression

I can keep the method static and edit it using the extension method by doing it in this manner: WithSplittedUnion, for instance ()

instead of the conventional approach, I'm now utilizing the following:

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

The explanation should be clear, I appreciate you.

1
1
10/29/2019 8:26:46 AM

Accepted Answer

What you need, in my opinion, isExpressionVisitor to navigate and alterExpressionTree . The manner you call is one thing I would alter.Any . In place of

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

I'd choose

(
    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(...)

It's simpler for me to put together,ExpressionTree and the result will be same.

Sample of a code

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

You may refer to it as follows:

OptionalCallFix.Fix(context, ctx => ctx.Entity.ListA.Union(ctx.ListB));
2
10/29/2019 8:38:35 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow