undefined
undefined

Reputation: 34248

Expression.Lambda with multiple input selectors

I am trying to create my own custom operation which I can use in the database to find rows which are affected by a change in a value.

Im looking at Jon Skeets between operator example here: LINQ Between Operator but im having trouble as my operation contains multiple parameter inputs

    public static IQueryable<TSource> LeavingRange<TSource, TKey>(this IQueryable<TSource> source,
        Expression<Func<TSource, TKey>> lowKeySelector,
        Expression<Func<TSource, TKey>> highKeySelector,
        Nullable<TKey> oldValue,
        Nullable<TKey> newValue)
            where TKey : struct, IComparable<TKey>
    

As you can see I have 2 selectors, however I'm not too sure how to combine these correctly into the parameters for my Expression.Lambda call. I have tried just putting the parameters from both input expressions into the lambda as parameters but i think im missing something.

Expression.Lambda<Func<TSource, bool>>(isLeavingRange, lowKeySelector.Parameters[0], highKeySelector.Parameters[0]);

Doing this gives the following error:

Incorrect number of parameters supplied for lambda declaration

What is the correct way to combine the input parameters when constructing a Lambda?


Supporting Info

My full code is below, but I think the relevant bits are the two selectors and the Expression.Lambda call

    public static IQueryable<TSource> LeavingRange<TSource, TKey>(this IQueryable<TSource> source,
        Expression<Func<TSource, TKey>> lowKeySelector,
        Expression<Func<TSource, TKey>> highKeySelector,
        Nullable<TKey> oldValue,
        Nullable<TKey> newValue)
            where TKey : struct, IComparable<TKey>
    {
        Expression lowKey = Expression.Invoke(lowKeySelector, lowKeySelector.Parameters.ToArray());
        Expression highKey = Expression.Invoke(highKeySelector, highKeySelector.Parameters.ToArray());

        //is oldValue null which means it cant possibly be leaving
        var oldValueIsNotNull = Expression.NotEqual(Expression.Constant(oldValue, typeof(Nullable<TKey>)), Expression.Constant(null, typeof(Nullable<TKey>)));
        var newValueIsNull = Expression.Equal(Expression.Constant(newValue, typeof(Nullable<TKey>)), Expression.Constant(null, typeof(Nullable<TKey>)));
        var newValueIsNotNull = Expression.Not(newValueIsNull);

        var oldValueIsBetweenRange = Between(Expression.Convert(Expression.Constant(oldValue), typeof(TKey)), lowKey, highKey);
        var newValueIsNotBetweenRange = Expression.Not(Between(Expression.Convert(Expression.Constant(newValue), typeof(TKey)), lowKey, highKey));

        //IE leaving because its going from in the range to null
        var newValueIsNullAndOldValueIsBetweenRange = Expression.AndAlso(newValueIsNull, oldValueIsBetweenRange);

        var oldValueIsInRangeAndNewValueIsNot = Expression.AndAlso(newValueIsNotNull, Expression.AndAlso(oldValueIsBetweenRange, newValueIsNotBetweenRange));
        var isLeavingRange = Expression.AndAlso(oldValueIsNotNull, Expression.Or(newValueIsNullAndOldValueIsBetweenRange, oldValueIsInRangeAndNewValueIsNot));

        var leavingRange = Expression.Lambda<Func<TSource, bool>>(isLeavingRange, lowKeySelector.Parameters[0], highKeySelector.Parameters[0]);
        
        return source.Where(leavingRange);
    }

Upvotes: 1

Views: 612

Answers (1)

Ripple
Ripple

Reputation: 1265

A delegate passed to Where() takes each only one element in the collection as an argument, so you need to make the two expressions invoking lowKeySelector and highKeySelector take that same element (same instance of the ParameterExpression) as an argument and also need to build the lambda expression to use that as a parameter.

public static IQueryable<TSource> LeavingRange<TSource, TKey>(this IQueryable<TSource> source,
     Expression<Func<TSource, TKey>> lowKeySelector,
     Expression<Func<TSource, TKey>> highKeySelector,
     Nullable<TKey> oldValue,
     Nullable<TKey> newValue)
         where TKey : struct, IComparable<TKey>
{
    ParameterExpression paramOfWhereDelg = Expression.Parameter(typeof(TSource), "p");

    Expression lowKey = Expression.Invoke(lowKeySelector, paramOfWhereDelg);
    Expression highKey = Expression.Invoke(highKeySelector, paramOfWhereDelg);

    // Build your expression tree
    //  ...

    var leavingRange = Expression.Lambda<Func<TSource, bool>>(isLeavingRange, paramOfWhereDelg);

    return source.Where(leavingRange);
}

(Or you can use lowKeySelector.Parameters instead of paramOfWhereDelg, but I believe creating an another ParameterExpression would make it easier to understand.)

Upvotes: 2

Related Questions