Выражение LINQ не может быть переведено для базового свойства - PullRequest
3 голосов
/ 22 марта 2019

У меня есть пользовательская реализация OrderBy, она работает только для типов без наследования, если я хочу упорядочить по полю из базового типа, который я получил Выражение LINQ не может быть переведено

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);
}

Я использую entityframework core 2.2, но очень интересно то, что если я пишу просто source.OrderBy(x=>x.someBaseField), то это работает без проблем, поэтому что-то должно произойти с моей пользовательской реализацией

В журнале ошибок я также получил переведенный запрос, и это выглядит так, что это интересная часть конца

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]>))

1 Ответ

5 голосов
/ 22 марта 2019

Я видел нечто подобное раньше.Единственная разница между сгенерированным компилятором и ручным выражением заключается в свойстве ReflectedType PropertyInfo - в сгенерированном компилятором коде оно совпадает с DeclaringType, который в данном случае является базовым классом, тогда как в PropertyInfo, полученном с помощьюtype.GetProperty это производный тип, используемый для его получения.

По какой-то неизвестной причине (возможно, ошибка) это сбивает с толку EF Core.Временное решение: изменить код следующим образом:

var property = type.GetProperty(orderByProperty);
if (property.DeclaringType != property.ReflectedType)
    property = property.DeclaringType.GetProperty(property.Name);

или использовать вспомогательный метод, подобный этому

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;
}

Для поддержки вложенных свойств я бы добавил следующеепомощники

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));

, а затем используйте

var propertyAccess = Property(param, orderByProperty);

и

new Type[] { type, orderByExpression.ReturnType },

внутри рассматриваемого метода.

...