Entity Framework OrderBy on field from projection

961 views Asked by At

I want to sort on a field only known at runtime, and that field may be constructed inside the select clause.

Requirements:

  1. User should be able to sort on any column;
  2. That column may come from an entity or projection class;
  3. Programmer decides if want to return the same source entity, or a projection.

Because of (3), straightforward implementation wouldn't find a field from projection class, as I have a static type of the base/entity type. Then I began to change code to consider the resulting type instead.

I have a feeling that it is just static typing getting in the way, that is not really helping here. Unfortunately IQueryable<dynamic> doesn't work.

Sample code (details omitted for brevity):

void Main()
{
    using (var db = new MyContext())
    {
        var original = db.Set<Sample>();

        // requirement (3), static type Sample, dynamic type SampleDTO
        Expression<Func<Sample, Sample>> fn = item => new SampleDTO() { Description = "Value: " + item.Name};
        var projected = original.Select(fn);

        // requirements (1) and (2)
        var ordered = OrderBy_Default<Sample, SampleDTO>(projected, "Description");
        ordered.Dump();
    }
}

private IQueryable<T> OrderBy_Default<T, TProject>(IQueryable<T> source, string ordering)
{
  var type = source.ElementType;
  var resultType = type;
  var property = resultType.GetProperty(ordering, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
  var parameter = Expression.Parameter(resultType, "p");
  var propertyAccess = Expression.MakeMemberAccess(parameter, property);
  var orderByExp = Expression.Lambda(propertyAccess, parameter);
  MethodCallExpression resultExp = Expression.Call(typeof(Queryable),
      "OrderBy",
      new Type[] { type, property.PropertyType },
      source.Expression, Expression.Quote(orderByExp));
  return source.Provider.CreateQuery<T>(resultExp);
}

The previous code produces this error:

No generic method 'OrderBy' on type 'Queryable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.

I have tried projected.Cast<SampleDTO>():

Unable to cast the type 'Sample' to type 'UserQuery+SampleDTO'. LINQ to Entities only supports casting EDM primitive or enumeration types.

I have tried projected.OfType<SampleDTO>():

'UserQuery+SampleDTO' is not a valid metadata type for type filtering operations. Type filtering is only valid on entity types and complex types.

Cast() and OfType() works with L2O, but not with L2EF.

Adding SampleDTO to entity model of course is not the answer (have tried it though, for the sake of knowing how it would work).

My entities & projections:

public class Sample
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class SampleDTO: Sample
{
    public string Description { get; set; }
}

I made SampleDTO inherit from Sample, it case it helps. That's is not a requirement, it's a matter of convenience.

Some possible answer:

Instead of Expression<Func<Sample, Sample>> I could have Expression<Func<Sample, SampleDTO>>. That invalidates requirement (3) and would break much of my existing code.

PS: I'm considering using the Microsoft Dynamic LINQ library, but for the sake of the answer I'm looking forward to know what's really going on.

1

There are 1 answers

1
Catwood On

This has worked for me, if i understand your question correctly but i get the feeling i am missing something

    public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering)
    {
        var type = typeof(T);
        var property = type.GetProperty(ordering);
        var parameter = Expression.Parameter(type);
        var propertyAccess = Expression.MakeMemberAccess(parameter, property);
        var orderByExp = Expression.Lambda(propertyAccess, parameter);
        MethodCallExpression resultExp = Expression.Call(typeof(Queryable), "OrderBy", new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
        return source.Provider.CreateQuery<T>(resultExp);
    }