1

I am creating an advanced search that converts an OData expression into a .NET expression tree (Expression<Func<T, bool>>). I pass this expression into my EF6 .Select() method as a predicate and it works as expected.

However, in implementing this feature, I've discovered that LINQ methods only work with IQueryable<TSource>. This works for .Set<T>(), but I won't know the type at runtime, so I need to use .Set().

I could probably use reflection to call .Set<T>() and then invoke it, but that seems like a bit of a hack, so I'd rather do it directly through .Set() if at all possible.

oscilatingcretin
  • 10,457
  • 39
  • 119
  • 206

1 Answers1

1

If I understand correctly, you have LambdaExpression instead of Expression<Func<T, bool>> and you want to use it as Where but on IQueryable (which DbSet class implements) rather than IQueryable<T>.

All you need to know is that IQueryable<T> extensions methods simply emit MethodCallExpression to the corresponding Queryable method in the query expression tree.

For instance, to emulate Where or Select on IQueryable you can use the following custom extension methods:

public static class QueryableExtensions
{
    public static IQueryable Where(this IQueryable source, LambdaExpression predicate)
    {
        var expression = Expression.Call(
            typeof(Queryable), "Where",
            new Type[] { source.ElementType },
            source.Expression, Expression.Quote(predicate));
        return source.Provider.CreateQuery(expression);
    }

    public static IQueryable Select(this IQueryable source, LambdaExpression selector)
    {
        var expression = Expression.Call(
            typeof(Queryable), "Select",
            new Type[] { source.ElementType, selector.Body.Type },
            source.Expression, Expression.Quote(selector));
        return source.Provider.CreateQuery(expression);
    }
}

You can do similar for other Queryable methods needed.

Update: Since you are interesting in, here is an example of using expression prototypes to get a generic method definition and construct generic method from it:

public static class QueryableExtensions
{
    static MethodInfo QueryableMethod<T>(this Expression<Func<IQueryable<object>, T>> prototype, params Type[] types)
    {
        return ((MethodCallExpression)prototype.Body).Method
            .GetGenericMethodDefinition()
            .MakeGenericMethod(types);
    }

    public static IQueryable Where(this IQueryable source, LambdaExpression predicate)
    {
        var expression = Expression.Call(
            QueryableMethod(q => q.Where(x => true), source.ElementType),
            source.Expression, Expression.Quote(predicate));
        return source.Provider.CreateQuery(expression);
    }

    public static IQueryable Select(this IQueryable source, LambdaExpression selector)
    {
        var expression = Expression.Call(
            QueryableMethod(q => q.Select(x => 1), source.ElementType, selector.Body.Type),
            source.Expression, Expression.Quote(selector));
        return source.Provider.CreateQuery(expression);
    }
}
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • That's awesome. Works perfectly. Any idea how to get the MethodInfo of the LINQ methods using expressions so you don't have to use a string? I tried for a couple hours, but got hung up on matching the overloads' signatures that use generic types (usually, you can just pass a default type, but I don't know how to do it with generics). – oscilatingcretin Oct 26 '16 at 18:14
  • Why bother with MethodInfo - the names of the methods will not change, and also if you are on C#6 you could always use `nameof(Queryable.Select)` – Ivan Stoev Oct 26 '16 at 18:17
  • I swear I tried nameof, but it was giving me some issues. Just tried it again and it works, so I was doing something wrong. Agreed, the names will never change, but I tend to avoid magic strings wherever possible. Thanks again – oscilatingcretin Oct 26 '16 at 18:25
  • No problem. See the update for a way to accomplish what you asked in the first comment :) Cheers. – Ivan Stoev Oct 26 '16 at 19:38