EF Core, append to predicate builder in Any condition

923 Views Asked by At

I'm trying to merge two predicates in then Any clause of the following code, but i cannot find a way to do this.

private static Expression<Func<Order, bool>> BuildWhereExpression(DataFilterOrder filter, AppDbContext dbContext)
{

var predicate = PredicateBuilder.True<Order>();

var confirmationPredicate = PredicateBuilder.True<HConfirmation>();

if (!string.IsNullOrWhiteSpace(filter.ConfirmationNumber))
{
  confirmationPredicate = confirmationPredicate.And(r =>
  r.Confirmation.Document.Number == filter.ConfirmationNumber);
}
if (filter.ConfirmationDateFrom != null)
{
  confirmationPredicate = confirmationPredicate.And(r =>
  r.Confirmation.Document.Date >= filter.ConfirmationDateFrom);
}
.....


predicate = predicate.And(o =>
   dbContext.Confirmations
   .Join(
     dbContext.DocumentHierarchies,
     c => c.DocumentId,
     h => h.ChildDocumentId,
     (c, h) => new HConfirmation { Confirmation = c, Hierarchy = h })
   .Any(r => r.Hierarchy.ParentDocumentId == o.DocumentId && 
   ???confirmationPredicate???)

  return predicate;
}
....

// called by

  var wherePredicate = BuildWhereExpression(filter, dbContext);


  var list = await dbContext.Orders
    .Where(wherePredicate)
    .ToListAsync();

Any help? Thanks very much.

PredicateBuilder class:

public static class PredicateBuilder
    {

        // Creates a predicate that evaluates to true.        
        public static Expression<Func<T, bool>> True<T>() { return param => true; }

        // Creates a predicate that evaluates to false.        
        public static Expression<Func<T, bool>> False<T>() { return param => false; }

        // Creates a predicate expression from the specified lambda expression.        
        public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> predicate) { return predicate; }
        public static Expression<Func<T, bool>> Create1<T, K>(Expression<Func<T, bool>> predicate, K obj) { return predicate; }

        // Combines the first predicate with the second using the logical "and".        
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.AndAlso);
        }

        // Combines the first predicate with the second using the logical "or".        
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.OrElse);
        }

        // Negates the predicate.        
        public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression)
        {
            var negated = Expression.Not(expression.Body);
            return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters);
        }

        // Combines the first expression with the second using the specified merge function.        
        static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
        {
            // zip parameters (map from parameters of second to parameters of first)
            var map = first.Parameters
                .Select((f, i) => new { f, s = second.Parameters[i] })
                .ToDictionary(p => p.s, p => p.f);

            // replace parameters in the second lambda expression with the parameters in the first
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);

            // create a merged lambda expression with parameters from the first expression
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }

        class ParameterRebinder : ExpressionVisitor
        {
            readonly Dictionary<ParameterExpression, ParameterExpression> map;

            ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
            {
                this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
            }

            public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
            {
                return new ParameterRebinder(map).Visit(exp);
            }

            protected override Expression VisitParameter(ParameterExpression p)
            {
                ParameterExpression replacement;
                if (map.TryGetValue(p, out replacement))
                {
                    p = replacement;
                }
                return base.VisitParameter(p);
            }
        }
    }
1

There are 1 best solutions below

0
Svyatoslav Danyliv On

Well, tried to simplify solution, but looks like it is needed to build Any part dynamically. Sorry not tested and some small mistakes can be here.

private static Expression<Func<Order, bool>> BuildWhereExpression(DataFilterOrder filter, AppDbContext dbContext)
{
    var predicate = PredicateBuilder.True<Order>();

    var confirmationPredicate = PredicateBuilder.True<HConfirmation>();

    if (!string.IsNullOrWhiteSpace(filter.ConfirmationNumber))
    {
        confirmationPredicate = confirmationPredicate.And(r =>
        r.Confirmation.Document.Number == filter.ConfirmationNumber);
    }
    if (filter.ConfirmationDateFrom != null)
    {
        confirmationPredicate = confirmationPredicate.And(r =>
        r.Confirmation.Document.Date >= filter.ConfirmationDateFrom);
    }
    .....

    // we can write this query separately
    var confirmations = dbContext.Confirmations
        .Join(dbContext.DocumentHierarchies,
            c => c.DocumentId,
            h => h.ChildDocumentId,
            (c, h) => new HConfirmation { Confirmation = c, Hierarchy = h }
        );

    var orderParam = Expression.Parameter(typeof(Order), "o");
    var hConfirmationParam = Expression.Parameter(typeof(HConfirmation), "r");

    // r.Hierarchy.ParentDocumentId == o.DocumentId
    var anyPredicate = (Expression)Expression.Equal(Expression.Property(Expression.Property(hConfirmationParam, "Hierarchy"), "ParentDocumentId"),
        Expression.Property(orderParam, "DocumentId"));

    // r.Confirmation
    var confirmationAccess = Expression.Property(hConfirmationParam, "Confirmation");
    // correcting confirmation predicate
    var confirmationPredicateCorrected = ExpressionReplacer.GetBody(confirmationPredicate, confirmationAccess);

    // r.Hierarchy.ParentDocumentId == o.DocumentId && confirmationPredicateCorrected
    anyPredicate = Expression.AndAlso(anyPredicate, confirmationPredicateCorrected);
    // r => r.Hierarchy.ParentDocumentId == o.DocumentId && confirmationPredicateCorrected
    var anyLambda = Expression.Lambda(anyPredicate, hConfirmationParam);

    var anyCall = Expression.Call(typeof(Queryable), "Any", new [] { typeof(HConfirmation) }, confirmations.Expression, Expression.Quote(anyLambda));

    var additionalPredicate = Expression.Lambda<Func<Order, bool>>(anyCall, orderParam);

    predicate = predicate.And(additionalPredicate);

    return predicate;
}

Anyawy, additional helper class is needed:

public class ExpressionReplacer : ExpressionVisitor
{
    readonly IDictionary<Expression, Expression> _replaceMap;

    public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
    {
        _replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
    }

    public override Expression Visit(Expression exp)
    {
        if (exp != null && _replaceMap.TryGetValue(exp, out var replacement))
            return replacement;
        return base.Visit(exp);
    }

    public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
    {
        return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
    }

    public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
    {
        return new ExpressionReplacer(replaceMap).Visit(expr);
    }

    public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
    {
        if (lambda.Parameters.Count != toReplace.Length)
            throw new InvalidOperationException();

        return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count)
            .ToDictionary(i => (Expression) lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
    }
}

Strongly recommend this extension for Expression Tree visualization and debugging: https://marketplace.visualstudio.com/items?itemName=vs-publisher-1232914.ReadableExpressionsVisualizers