Search code examples
entity-framework-6dbset

Selecting from type-based DbSet using .Set() instead of .Set<T>()


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.


Solution

  • 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);
        }
    }