Tengo una implementación personalizada de OrderBy, solo funciona para tipos sin herencia, si quiero ordenar por campo desde el tipo base obtuve La expresión LINQ no se pudo traducir
public static IOrderedQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByProperty, bool desc)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
if (string.IsNullOrEmpty(orderByProperty))
{
throw new ArgumentNullException(nameof(orderByProperty));
}
var command = desc ? "OrderByDescending" : "OrderBy";
var type = typeof(TEntity);
var param = Expression.Parameter(type, "p");
var property = type.GetProperty(orderByProperty);
var propertyAccess = Expression.MakeMemberAccess(param, property);
var orderByExpression = Expression.Lambda(propertyAccess, param);
var resultExpression = Expression.Call(
typeof(Queryable),
command,
new Type[] { type, property.PropertyType },
source.Expression,
Expression.Quote(orderByExpression));
return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery(resultExpression);
}
Estoy usando entityframework core 2.2 pero lo interesante es que si escribo solo source.OrderBy(x=>x.someBaseField)
entonces funciona sin ningún problema, por lo que debe haber algo con mi implementación personalizada
En el registro de errores también obtuve la consulta traducida y se ve así, lo interesante es la parte final
orderby new SomeType() {NewField = [entity].DbField, Id = [entity].Id}.Id desc
orderByExpression.Body {p => p.Id}
resultExpression
.Call System.Linq.Queryable.OrderByDescending(
.Call System.Linq.Queryable.Select(
.Call System.Linq.Queryable.Where(
.Call System.Linq.Queryable.Where(
.Constant<Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[MyTypeView]>(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[MyTypeView]),
'(.Lambda #Lambda1<System.Func`2[MyTypeView,System.Boolean]>)),
'(.Lambda #Lambda2<System.Func`2[MyTypeView,System.Boolean]>)),
'(.Lambda #Lambda3<System.Func`2[MyTypeView, MyTypeResult]>))
,
'(.Lambda #Lambda4<System.Func`2[MyTypeResult,System.Guid]>))
He visto algo como esto antes. La única diferencia entre el compilador generado y la expresión manual es la propiedad ReflectedType
de PropertyInfo
: en el código generado por el compilador es lo mismo que DeclaringType
, que en este caso es la clase base, mientras que en PropertyInfo
obtiene por tipo. type.GetProperty
es el tipo derivado solía obtenerlo.
Por alguna razón desconocida (probablemente un error) esto confunde a EF Core. La solución alternativa es cambiar el código de la siguiente manera:
var property = type.GetProperty(orderByProperty);
if (property.DeclaringType != property.ReflectedType)
property = property.DeclaringType.GetProperty(property.Name);
o use un método auxiliar como este
static PropertyInfo GetProperty(Type type, string name)
{
for (; type != null; type = type.BaseType)
{
var property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
if (property != null) return property;
}
return null;
}
Para admitir propiedades anidadas, agregaría los siguientes ayudantes
static Expression Property(Expression target, string name) =>
name.Split('.').Aggregate(target, SimpleProperty);
static Expression SimpleProperty(Expression target, string name) =>
Expression.MakeMemberAccess(target, GetProperty(target.Type, name));
y luego usar
var propertyAccess = Property(param, orderByProperty);
y
new Type[] { type, orderByExpression.ReturnType },
dentro del método en cuestión.