Search code examples
c#linqexpression-treesdynamic-linq

Reusable LINQ method query


Is it possible to create a reusable LINQ query expression with calls to OrderBy and Where, without applying it to a specific IQueryable?

I would like to be able to do something like this:

var data = new List<PlayerDTO>();
var builder = new Builder<PlayerDTO>();
var query = builder.GetQuery();
var results = data.AsQueryable().Provider.CreateQuery(query);

I have trouble getting this to work without providing the IQueryable object to the GetQuery method.

This is the code I have so far:

public Expression GetQuery()
{
    var type = typeof(T); // T is PlayerDTO
    var col = type.GetProperty(OrderBy.Column);

    var orderByMethod = typeof(Queryable).GetMethods().Single(
        method => method.Name == "OrderBy"
                  && method.IsGenericMethodDefinition
                  && method.GetGenericArguments().Length == 2
                  && method.GetParameters().Length == 2);

    var genericOrdebyMethod = orderByMethod.MakeGenericMethod(typeof(T), col.PropertyType);

    var parameter = Expression.Parameter(typeof(T), "p"); // {p}
    var property = Expression.Property(parameter, col); // {p.ID}
    var lambda = Expression.Lambda<Func<T, int>>(property, parameter); // {p => p.ID}

    //This list should not exist here
    var tempList = new List<PlayerDTO>() { new PlayerDTO(1, "First"), new PlayerDTO(2, "Second") };

    var orderByMethodExpression = Expression.Call(genericOrdebyMethod, tempList.AsQueryable().Expression, lambda); // {tempList.OrderBy(p => p.ID)}

    var results = tempList.AsQueryable().Provider.CreateQuery(orderByMethodExpression);

    return orderByMethodExpression;
}

The relevant part is the call to Expression.Call, where I had to provide an IQueryable so it would work, but I would like to be able to build the expression without specifying an existing IQueryable.

Is this even possible?

Edit:
Now that I think about this, I actually don't need to do this at all... It makes perfect sense to just send the IQueryable as a parameter to the GetQuery method. I will keep this question up though.


Solution

  • You could create it as another expression from IQueryable to IOrderedQueryable, something like:

    public Expression getOrderByQuery<T>(PropertyInfo col)
    {
        var orderByMethod = typeof(Queryable).GetMethods().Single(
            method => method.Name == "OrderBy"
                      && method.IsGenericMethodDefinition
                      && method.GetGenericArguments().Length == 2
                      && method.GetParameters().Length == 2);
    
        var genericOrdebyMethod = 
                            orderByMethod.MakeGenericMethod(typeof(T), col.PropertyType);
    
        var parameter = Expression.Parameter(typeof(T), "p"); // {p}
        var property = Expression.Property(parameter, col); // {p.ID}
        var lambda = Expression.Lambda<Func<T, int>>(property, parameter); // {p => p.ID}
    
        var paramList = Expression.Parameter(typeof(IQueryable<T>));
    
        // {tempList.OrderBy(p => p.ID)}    
        var orderByMethodExpression = Expression.Call(genericOrdebyMethod, paramList, lambda); 
    
        return Expression.Lambda(orderByMethodExpression, paramList);
    }
    

    I use it this way, and my list get ordered:

    var tempList = new List<PlayerDTO>() 
      { 
         new PlayerDTO(2, "First"), 
         new PlayerDTO(1, "Second") 
      };
    
    var e = (Expression<Func<IQueryable<PlayerDTO>, IOrderedQueryable<PlayerDTO>>>)
                   getOrderByQuery<PlayerDTO>(typeof(PlayerDTO).GetProperty("Id"));
    e.Compile().Invoke(tempList.AsQueryable()).Dump();