Search code examples
c#linqlinq-to-entitieslinq-to-objectsexpressionvisitor

LINQ extension method to create Where query


I am trying to create an extension method that will be usable for both LINQ-to-Object and LINQ-to-Entities for creating a functioning Where query. More will go into it eventually but to start I am having an issue just getting the method to take a lambda column selection and use it as the base of a Contains() call. I have been able to make something work for LINQ-to-Objects, but when I try to use it for LINQ-to-Entities it has an issue.

Here is what works for LINQ-to-Objects:

public static IQueryable<T> WhereContains<T>(this IQueryable<T> query, Expression<Func<T, string>> column, IList<string> values)
{
    return query.Where(o => values.Contains(column.Compile().Invoke(o)));
}

If this is run against Entity Framework I get an exception stating

LINQ to Entities does not recognize the method...

I have a feeling this is going to require using an ExpressionVisitor, but I haven't been able to figure out how it needs to be wired in. Has anyone been able to accomplish this?

Update:

I would argue that this is not a duplicate being that the answer provided by xanatos showed a straight forward way of accomplishing this without using an ExpressionVisitor.


Solution

  • Yes, you have to modify a little the Expression, but you don't need an ExpressionVisitor. It is much more simple.

    public static IQueryable<TSource> WhereContains<TSource, TResult>(this IQueryable<TSource> query, Expression<Func<TSource, TResult>> column, IList<TResult> values)
    {
        MethodInfo iListTResultContains = typeof(ICollection<>).MakeGenericType(typeof(TResult)).GetMethod("Contains", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(TResult) }, null);
    
        var contains = Expression.Call(Expression.Constant(values), iListTResultContains, column.Body);
    
        var lambda = Expression.Lambda<Func<TSource, bool>>(contains, column.Parameters);
    
        return query.Where(lambda);
    }
    

    Note that I've expanded it a little to cover more than string.