-1

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.

Shahin Dohan
  • 6,149
  • 3
  • 41
  • 58
  • Can't you just use `Expression> = p => p.ColumnName;`? You can use that expression in your `OrderBy` method. This would work if your `PlayerDTO` implements some interface to know the columnName property. Otherwise you can still use reflection just for the property. – Alexander Derck Dec 27 '16 at 07:48
  • Yes I could, but my main problem is with the OrderBy/Where/etc type methods that I would like to include in the expression, then reuse on multiple IQueryables. – Shahin Dohan Dec 27 '16 at 08:04
  • It's just a bit weird what you're doing, dynamically building an expression, using Provider to turn it into an iQueryable, while you know exactly beforehand which property you will use and which method. The Linq extensionmethods on `IQueryable` actually do exactly the same thing you're trying to do manually now (see [this link](https://referencesource.microsoft.com/#System.Core/System/Linq/IQueryable.cs,7a2005ae434e9d9d,references)). – Alexander Derck Dec 27 '16 at 08:08
  • I'm not sure if I quite understand.. The property by which I want to order the list is only known at runtime. Then based on that property, I want to build an OrderBy expression with some filters and return it from the method. I will update my question with what I'm trying to do! – Shahin Dohan Dec 27 '16 at 08:32
  • 1
    Oh in that case I would make an extension method which takes an `IQueryable` and returns `IQueryable`, that would make much more sense. For example: `var result = data.AsQueryable().ModifyQuery();` – Alexander Derck Dec 27 '16 at 08:36
  • I wanted to avoid needing an instance of an IQueryable so I can have a nice generic method that gives me a reusable expression, but now that I think about it, I actually don't have any use for it... Thanks! – Shahin Dohan Dec 27 '16 at 08:43
  • This question is confusing and does not make a lot of sense. A LINQ expression is a tree, where the IQueryable is an integral part of it. You probably want to have a Func that adds ordering. http://stackoverflow.com/questions/40565859/cant-build-lambda-expression-with-unknown-type-of-a-property/40578938#40578938 – MBoros Dec 28 '16 at 08:39
  • @Mboros I'm only now learning about expression trees and all the details surrounding how IQueryable's work, so I maybe have messed up with the terminology. My problem wasn't with expressions, but rather with method calls (f.ex OrderBy) that takes an expression as its argument, then "compile" that and re-use it on any IQueryable that matches the type. The provided answer isn't 100% correct but achieves the goal. I don't need this anymore though! – Shahin Dohan Dec 28 '16 at 11:41

1 Answers1

1

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();
Maksim Simkin
  • 9,561
  • 4
  • 36
  • 49
  • Thanks Maksim, that worked! I find the casting and calling of Compile really ugly though, is there any way to make it cleaner or is this as simple as it gets? – Shahin Dohan Dec 27 '16 at 07:14
  • @ShahinDohan No, unfortunately i don't know it, you could cast in your method, bur as far as i know, somewhere should be cast and compile. – Maksim Simkin Dec 27 '16 at 07:31
  • I decided not to do this at all, but will keep the question up if anyone else does. Thanks in any case! – Shahin Dohan Dec 27 '16 at 08:48