Search code examples
c#linqlambdaexpressionexpression-trees

The type arguments for method Queryable.Select cannot be inferred from the usage


I am using a LambdaExpression to select columns dynamically:

var property = "power_usage"; // I set this dynamically.
var entityType = typeof(system_state);
var prop = entityType.GetProperty(property);
var source = Expression.Parameter(entityType, "ss");

var func = typeof(Func<,>);
var genericFunc = func.MakeGenericType(typeof(system_state), prop.PropertyType);

var linqQuery = context.system_state
            .Where(ss => ss.time_stamp >= StartDate && ss.time_stamp <= EndDate)
            .Select(genericFunc, Expression.PropertyOrField(source, property), source);

The variable genericFunc is supposed to define the delegateType, but I still get this error. What am I doing wrong?


Solution

  • I'll quote @Ivan Stoev

    The problem is that TResult needs to be known at compile time, so linqQuery (must) be IQueryable<string>, or IQueryable<int> etc. It's not possible to resolve the type of var at runtime. It's possible to emit the call to Select dynamically, but all you can get will be a non generic IQueryable, which is not so useful. You might take a look at DynamicLINQ package to see how it is addressing this and similar problems. But even with it it's a pain to work with untyped IQueryable.

    Then if you really want to do it... In the end it is your code :-)

    This can be cached:

    private static readonly MethodInfo selectT = (from x in typeof(Queryable).GetMethods(BindingFlags.Public | BindingFlags.Static)
                                                    where x.Name == nameof(Queryable.Select) && x.IsGenericMethod
                                                    let gens = x.GetGenericArguments()
                                                    where gens.Length == 2
                                                    let pars = x.GetParameters()
                                                    where pars.Length == 2 &&
                                                        pars[0].ParameterType == typeof(IQueryable<>).MakeGenericType(gens[0]) &&
                                                        pars[1].ParameterType == typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(gens))
                                                    select x).Single();
    

    Then:

    var property = "power_usage"; // I set this dynamically.
    var entityType = typeof(system_state);
    var prop = entityType.GetProperty(property);
    var source = Expression.Parameter(entityType, "ss");
    
    var func = typeof(Func<,>);
    var genericFunc = func.MakeGenericType(typeof(system_state), prop.PropertyType);
    
    var baseQuery = context.system_state
                .Where(ss => ss.time_stamp >= StartDate && ss.time_stamp <= EndDate);
    
    var exp = Expression.Lambda(Expression.Property(source, prop), source);
    
    MethodInfo select = selectT.MakeGenericMethod(entityType, prop.PropertyType);
    
    IQueryable query = (IQueryable)select.Invoke(null, new object[] { baseQuery, exp });
    
    var result = query.Cast<object>().ToArray();
    

    Note that I'm obtaining a non-generic IQueryable... Then I cast its elements to object and do a ToArray(), but you can do whatever you want with it. Underneath the IQueryable will be strongly typed, so it will be a IQueryable<int> or a IQueryable<something>, so you can cast it back to the "real" interface (IQueryable<T> inherits from IQueryable)