Highlight expressions between Linq and Entity and Linq for objects

I am trying to "split" a set of conditions between a Linq to Entities call and other code in order to reduce possible inconsistencies in conditions between the two calls.

I started by announcing my terms:

private Func<DateTime, Status, bool> _submissionDateExpiredCondition =
(submissionDate, status) => submissionDate < DateTime.Now && status == Status.OK;

private Func<DateTime, Status, bool> _submissionDateWithinOneWeekCondition =
(submissionDate, status) => DateTime.Now < DbFunctions.AddDays(submissionDate, -7) && status == Status.Pending;

private Func<DateTime?, Status, bool> _bidValidityEndPeriodWithinThirtyDaysCondition =
(bidValidityEndPeriod, status) =>  bidValidityEndPeriod.HasValue && DateTime.Now < DbFunctions.AddDays(bidValidityEndPeriod.Value, -30) && (status == Status.OK);

      

Then I want to use these conditions inside a where clause, like in Linq to Entities where the call is and as functions in an if statement (or perhaps in a Linq to Objects call):

myRepository
    .FindAll()
    .Where(x => x.Property == "value" 
    && x.Data.AnotherProperty == true 
    && _submissionDateExpiredCondition(x.Data.Timestamp, x.Data.Status) 
    || _submissionDateWithinOneWeekCondition(x.Data.Timestamp, x.Data.Status) 
    || _bidValidityEndPeriodWithinThirtyDaysCondition(x.Data.ValidityStamp, x.Data.Status))

      

and (note that MyCustomObject is not the same type that was returned myRepository.FindAll()

)

private void ApplyConditions(List<MyCustomObject> items) {
    foreach(var x in items){
        if(_submissionDateExpiredCondition(x.Data.Timestamp, x.Data.Status)){
            x.Property = "condition 1";
        }
        else if(_submissionDateWithinOneWeekCondition(x.Data.Timestamp, x.Data.Status))
        {
            x.Property = "condition 2";
        }
        else if(_bidValidityEndPeriodWithinThirtyDaysCondition(x.Data.ValidityStamp, x.Data.Status))
        {
            x.Property = "condition 3";
        }
    }
}

      

But I keep running into common problems like The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.


when doing a repository request ...

I tried to build a predicate with a predicate builder (as per https://petemontgomery.wordpress.com/2011/02/10/a-universal-predicatebuilder/ ) but no luck.

Can anyone point me in the right direction?

+1


source to share


1 answer


Late to the party, but someone might find my way of attacking the problem helpful. However, this is not easy to do without some kind of expression manipulation.

Main problem: inside the predicate expression .Where

you have InvocationExpression

delegates (i.e. compiled code). EF has no way of knowing what logic is baked into these delegates and therefore cannot translate it into SQL. That's where the exception is thrown from.

The goal is to have a predicate lambda expression .Where

that is logically equivalent to yours, but understandable with EF. This means that we must get from

Expression<Func<EntityType, bool>> xPredicate = x =>
    x.Property == "value" &&
    x.Data.AnotherProperty == true && 
    _submissionDateExpiredCondition(x.Data.Timestamp, x.Data.Status)
    || ...;

      

to

Expression<Func<EntityType, bool>> xPredicate = x =>
    x.Property == "value" &&
    x.Data.AnotherProperty == true && 
    x.Data.Timestamp < DateTime.Now && x.Data.Status == Status.OK
    || ...;

      

to be used in

myRepository.FindAll().Where(xPredicate)

      

where EntityType

is the type of the query item returned Find

- one that differs from MyCustomObject

.

Note that the call to the delegate is replaced with its expression definition (lambda body) with parameters (lambda) submissionDate

and status

replaced with the appropriate call expression expressions.



If you define conditions as delegates, their internal logic is lost in the compiled code, so we need to start with lambda expressions, not delegate:

private Expression<Func<DateTime, Status, bool>> _xSubmissionDateExpiredCondition = (submissionDate, status) => submissionDate < DateTime.Now && status == Status.OK;
// getting the delegate as before (to be used in ApplyConditions) is trivial:
private Func<DateTime, Status, bool> _submissionDateExpiredCondition = _xSubmissionDateExpiredCondition.Compile();

// ... other conditions here

      

By using a lambda expression rather than a delegate, the compiler allows you to rewrite the original predicate as follows:

Expression<Func<EntityType, bool>> xPredicate = x =>
    x.Property == "value" &&
    x.Data.AnotherProperty == true && 
    _xSubmissionDateExpiredCondition.Compile()(x.Data.Timestamp, x.Data.Status)
    || ...;

      

that, of course, EF won't understand anything better than before. However, we have achieved that the internal logic of the condition is part of the expression tree. So, all that's missing is magic:

xPredicate = MAGIC(xPredicate);

      

What MAGIC

it does: Find the InvocationExpression

delegate that is the result of the method call Compile()

in the lambda expression and replace it with the lambda body, but be sure to replace the lambda parameters in the body with the call expression arguments.

And here is my implementation. In fact, MAGIC

it's called here Express.Prepare

, which is a little less nonspecific.

/// <summary>
/// Helps in building expressions.
/// </summary>
public static class Express
{

    #region Prepare

    /// <summary>
    /// Prepares an expression to be used in queryables.
    /// </summary>
    /// <returns>The modified expression.</returns>
    /// <remarks>
    /// The method replaces occurrences of <see cref="LambdaExpression"/>.Compile().Invoke(...) with the body of the lambda, with it parameters replaced by the arguments of the invocation.
    /// Values are resolved by evaluating properties and fields only.
    /// </remarks>
    public static Expression<TDelegate> Prepare<TDelegate>(this Expression<TDelegate> lambda) => (Expression<TDelegate>)new PrepareVisitor().Visit(lambda);

    /// <summary>
    /// Wrapper for <see cref="Prepare{TDelegate}(Expression{TDelegate})"/>.
    /// </summary>
    public static Expression<Func<T1, TResult>> Prepare<T1, TResult>(Expression<Func<T1, TResult>> lambda) => lambda.Prepare();

    /// <summary>
    /// Wrapper for <see cref="Prepare{TDelegate}(Expression{TDelegate})"/>.
    /// </summary>
    public static Expression<Func<T1, T2, TResult>> Prepare<T1, T2, TResult>(Expression<Func<T1, T2, TResult>> lambda) => lambda.Prepare();

    // NOTE: more overloads of Prepare here.

    #endregion

    /// <summary>
    /// Evaluate an expression to a simple value.
    /// </summary>
    private static object GetValue(Expression x)
    {
        switch (x.NodeType)
        {
            case ExpressionType.Constant:
                return ((ConstantExpression)x).Value;
            case ExpressionType.MemberAccess:
                var xMember = (MemberExpression)x;
                var instance = xMember.Expression == null ? null : GetValue(xMember.Expression);
                switch (xMember.Member.MemberType)
                {
                    case MemberTypes.Field:
                        return ((FieldInfo)xMember.Member).GetValue(instance);
                    case MemberTypes.Property:
                        return ((PropertyInfo)xMember.Member).GetValue(instance);
                    default:
                        throw new Exception(xMember.Member.MemberType + "???");
                }
            default:
                // NOTE: it would be easy to compile and invoke the expression, but it intentionally not done. Callers can always pre-evaluate and pass a captured member.
                throw new NotSupportedException("Only constant, field or property supported.");
        }
    }

    /// <summary>
    /// <see cref="ExpressionVisitor"/> for <see cref="Prepare{TDelegate}(Expression{TDelegate})"/>.
    /// </summary>
    private sealed class PrepareVisitor : ExpressionVisitor
    {
        /// <summary>
        /// Replace lambda.Compile().Invoke(...) with lambda body, where the parameters are replaced with the invocation arguments.
        /// </summary>
        protected override Expression VisitInvocation(InvocationExpression node)
        {
            // is it what we are looking for?
            var call = node.Expression as MethodCallExpression;
            if (call == null || call.Method.Name != "Compile" || call.Arguments.Count != 0 || call.Object == null || !typeof(LambdaExpression).IsAssignableFrom(call.Object.Type))
                return base.VisitInvocation(node);

            // get the lambda
            var lambda = call.Object as LambdaExpression ?? (LambdaExpression)GetValue(call.Object);

            // get the expressions for the lambda parameters
            var replacements = lambda.Parameters.Zip(node.Arguments, (p, x) => new KeyValuePair<ParameterExpression, Expression>(p, x));

            // return the body with the parameters replaced
            return Visit(new ParameterReplaceVisitor(replacements).Visit(lambda.Body));
        }
    }

    /// <summary>
    /// <see cref="ExpressionVisitor"/> to replace parameters with actual expressions.
    /// </summary>
    private sealed class ParameterReplaceVisitor : ExpressionVisitor
    {
        private readonly Dictionary<ParameterExpression, Expression> _replacements;

        /// <summary>
        /// Init.
        /// </summary>
        /// <param name="replacements">Parameters and their respective replacements.</param>
        public ParameterReplaceVisitor(IEnumerable<KeyValuePair<ParameterExpression, Expression>> replacements)
        {
            _replacements = replacements.ToDictionary(kv => kv.Key, kv => kv.Value);
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            Expression replacement;
            return _replacements.TryGetValue(node, out replacement) ? replacement : node;
        }
    }
}

      

+1


source







All Articles