6

I am trying to build contains expression.

private Expression<Func<T, bool>> Contains<T>(string property, IEnumerable<dynamic> values, T item)
{
    ParameterExpression pe = Expression.Parameter(item.GetType(), "c");
    Expression columnNameProperty = Expression.Property(pe, property);
    var someValueContain = Expression.Constant(values, values.GetType());
    var convertExpression = Expression.Convert(columnNameProperty, typeof(Guid));
    Expression expression = Expression.Call(someValueContain, "Contains", new Type[] { }, convertExpression);

    return Expression.Lambda<Func<T, bool>>(expression, pe);
}

at run time I got this exception.

"No method 'Contains' exists on type 'System.Data.Linq.DataQuery`1[System.Object]'."

the soultion was to cast values parameter to list

private Expression<Func<T, bool>> Contains<T>(string property, IEnumerable<dynamic> values, T item)
{
    ParameterExpression pe = Expression.Parameter(item.GetType(), "c");
    Expression columnNameProperty = Expression.Property(pe, property);
    Guidvalues = values.Cast<Guid>().ToList();
    var someValueContain = Expression.Constant(Guidvalues, Guidvalues.GetType());
    var convertExpression = Expression.Convert(columnNameProperty, typeof(Guid));
    Expression expression = Expression.Call(someValueContain, "Contains", new Type[] { }, convertExpression);

    return Expression.Lambda<Func<T, bool>>(expression, pe);
}

the problem that the values list is more that 10000 so the performance was low and I got this exception

"The incoming request has too many parameters. The server supports a maximum of 2100 parameters. Reduce the number of parameters and resend the request."

I there any way to build dynamically lambda expression that generate like this query

select * from x where id in (select id from y)
2
  • 1
    Contains is actually an extension method on System.Linq.Queryable. Commented Jul 2, 2015 at 6:38
  • Why is values DataQuery<object>? Where did you lose the concrete type? Commented Jul 2, 2015 at 6:54

1 Answer 1

3

This is just syntactic sugar getting the better of you :)

The problem is that Contains indeed is not a method on DataQuery<T> - it's a static method in System.Linq.Queryable. The C# compiler handles this for you via extension methods, but that's just the C# compiler - it's not a feature of IL, it's a feature of C#. So when you're manipulating expression trees or emitting raw IL, you have to handle it yourself:

private Expression<Func<T, bool>> Contains<T, V>
 (string property, IQueryable<V> values, T item)
{
        ParameterExpression pe = Expression.Parameter(item.GetType(), "c");
        Expression columnNameProperty = Expression.Property(pe, property);
        var someValueContain = Expression.Constant(values, values.GetType());
        var convertExpression = Expression.Convert(columnNameProperty, typeof(V));
        Expression expression = 
          Expression.Call
          (
            (
             ((Expression<Func<bool>>)
             (() => Queryable.Contains(default(IQueryable<V>), default(V)))
            )
            .Body as MethodCallExpression).Method, 
            someValueContain, 
            convertExpression
          );

        return Expression.Lambda<Func<T, bool>>(expression, pe);
}

I'd avoid using dynamic in LINQ queries as well - the way you're using it, it's no better than having IEnumerable<object>, and if you want it to always be Guid anyway, just make it generic :)

This method assumes that whatever type of the column is convertible to the type of the items in the enumerable you're passing, of course, but it will work for any type, not just Guid.

However, this will not solve the second problem you have - it's quite explicit. The enumerable you're passing has way too many items to pass. Unless your LINQ provider has a better way of passing the values, you'll have to resort to splitting the query into multiple separate queries, each for e.g. 1000 items, and then joining the results back together. Unless of course, my guess is right, and the values you're passing is in fact a queryable as well - in that case, the code should work just fine.

EDIT:

The best way I've found of getting the proper MethodInfo is a set of methods like this:

public static MethodInfo Method<TR>(Expression<Func<TR>> expression)
{
    return (expression.Body as MethodCallExpression).Method;
}

public static MethodInfo Method<T1, TR>(Expression<Func<T1, TR>> expression)
{
    return (expression.Body as MethodCallExpression).Method;
}

(autogenerated the same way the actual Func<...> delegates are)

This allows simplifying getting the method info to this:

Method((IQueryable<T> queryable, T item) => queryable.Contains(item))

Or alternatively (to avoid having to generate all the possible overloads):

Method(() => default(IQueryable<T>).Contains(default(T)))
Sign up to request clarification or add additional context in comments.

8 Comments

(() => Queryable.Contains).Method Never seen that approach before - clever!
@Rob I've started using it almost exclusively (though I usually put it in a static readonly field somewhere) - not only do you avoid trouble with finding the correct overload, but you can also rename the methods safely etc. A bit of extra compile-time protection :)) Although now that I'm looking at it, I actually made a mistake there. I'll have to fix it :D
@Aron I fixed it. I forgot I made a lot of extension methods and helper methods to make this a lot nicer than it is by default :D
Ah...could you add the extension method? Still trying to figure out some of it...
@Bakri Right, you need to make sure both of the queryables come from the same data context. I assume you're retrieving each on its own newly created context?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.