Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

dynamic

linq

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)
like image 887
Bakri Avatar asked Jul 02 '15 06:07

Bakri


1 Answers

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)))
like image 55
Luaan Avatar answered Oct 20 '22 01:10

Luaan