Re-implementation of OrderBy, ThenBy and Null TypeMapping in Sql Tree error

c# entity-framework-core expression-trees lambda linq

Question

I am trying to implement OrderBy and ThenBy in a different way to hide lambda expression from OrderBy and ThenBy extension methods. These extension methods accept classes which implement IOrderSpecification:

public class PersonOrderByAgeSpecification : OrderSpecification<Person>
{
    public PersonOrderByAgeSpecification(Sort direction= Sort.Ascending) : base(direction)
    {
    }

    public override Expression<Func<Person, IComparable>> AsExpression()
    {
        return personOrder => personOrder.Age;
    }
}

And the usage:

 var orderSpecification = new PersonOrderByAgeSpecification(Sort.Ascending); 
 var sortedPeople=  _dbContext.People.OrderBy(orderSpecification);

It works fine when the property type in AsExpression() is just string. For example:

public override Expression<Func<Person, IComparable>> AsExpression()
{
    return personOrder => personOrder.FirstName;
}

Otherwise I would get this error: (Does not work with integer or bool)

InvalidOperationException: Null TypeMapping in Sql Tree Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.RelationalSqlTranslatingExpressionVisitor+SqlTypeMappingVerifyingExpressionVisitor.VisitExtension(Expression node)

The source code is available here

I appreciate any help.

1
1
7/18/2019 8:15:02 AM

Accepted Answer

First off, you are using preview (beta) software, which is expected to have issues.

But the main problem is that LINQ ordering methods have second generic type argument TKey, which you are hiding behind IComparable, which for value types causes a hidden cast inside the expression.

Apart from unnecessary boxing, this is not a problem for LINQ to Objects provider because it simply compiles and executes a delegate from the lambda expression. However other IQueryable providers usually need to translate the expression to something else (usually SQL). Most of them identify such casts (Expression.Convert) and remove them during the processing. Apparently EF 3.0 preview you are using doesn't, hence the exception.

You can avoid such issues by eliminating the hidden casts yourself. It's possible to do that with expression manipulation, but the easiest is to introduce the second generic type argument to your base abstract class:

public abstract class OrderSpecification<T, TKey> : IOrderSpecification<T>

and change the abstract method signature to

public abstract Expression<Func<T, TKey>> AsExpression();

The implementation, interface and everything else except the concrete classes will remain as is.

Now all you need is to specify the actual key type in the inherited class and change the AsExpression override signature. For instance:

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

public class PersonAgeOrderSpecification : OrderSpecification<Person, int>
{
    public PersonAgeOrderSpecification(Sort direction) : base(direction) { }
    public override Expression<Func<Person, int>> AsExpression()
    {
        return person => person.Age;
    }
}

and everything will be fine.

2
7/18/2019 8:13:49 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow