Linq: запрашивается.OrderBy() с использованием набора выражений

#c# #linq #entity-framework #lambda

#c# #linq #entity-framework #лямбда

Вопрос:

Интересно, как я могу хранить выражения orderby в списке. Это то, что я хотел написать:

 List<Expression<Func<Products,Object>>> list = new List<Expression<Func<Products,Object>>>()
{
  p => p.Name,
  p => p.Id
};
  

Затем:

 var expr = list[0];
myProducts.OrderBy( expr );
  

который работает для p.Name , но не работает для p.Id ( list[1] ), поскольку он удаляет следующее исключение

Необработанное исключение типа ‘System.В EntityFramework.SQLServer произошло исключение NotSupportedException’.Дополнительная информация о dll: Не удалось преобразовать тип ‘System.Int32’ в тип ‘System.Объект’. LINQ для объектов поддерживает только приведение примитивов EDM или типов перечисления.

Какой тип списка я должен использовать?

Комментарии:

1. Обходной путь заключается в написании, p => p.Id.ToString() но я хотел бы, чтобы он также работал с System.Int32

2. @RobertFricke это не обходной путь, это также приведет к исключению NotSupportedException

3. @RobertFricke: Если вы упорядочите его как строку, тогда будет диапазон от 1 до 12 1, 10, 11, 12, 2, 3, 4, 5, 6, 7, 8, 9 , что не то, что вы хотите.

4. Вместо Func<Продукт, объект> попробуйте Func<Продукт, динамический>

5. @RandRandom, потому что я пытался.

Ответ №1:

Вот мое решение (с использованием отражения и на основе идей DynamicLinq):

Определение ConvertableExpression класса, чтобы мы могли перехватывать вызовы нашего пользовательского OrderBy() :

 public class ConvertableExpression<T>
{
    public ConvertableExpression(Expression<Func<T, object>> expr)
    {
        this.Expression = expr;
    }

    public Expression<Func<T, object>> Expression { get; private set; }
}
  

Представляем метод расширения для упрощения приведения из обычного Expression :

 public static class ExpressionExtensions
{
    public static ConvertableExpression<T> AsConvertable<T>(this Expression<Func<T, object>> expr)
    {
        return new ConvertableExpression<T>(expr);
    }
}
  

Расширение IQueryable с помощью реализации на основе отражения OrderBy() :

 public static class QueryableExtensions
{
    public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, ConvertableExpression<T> expr)
    {
        Expression queryExpr = source.Expression;

        var exprBody = SkipConverts(expr.Expression.Body);
        var lambda = Expression.Lambda(exprBody, expr.Expression.Parameters);
        var quote = Expression.Quote(lambda);

        queryExpr = Expression.Call(typeof(Queryable), "OrderBy", new[] { source.ElementType, exprBody.Type }, queryExpr, quote);

        return (IOrderedQueryable<T>)source.Provider.CreateQuery(queryExpr);
    }

    private static Expression SkipConverts(Expression expression)
    {
        Expression result = expression;

        while (result.NodeType == ExpressionType.Convert || result.NodeType == ExpressionType.ConvertChecked)
            result = ((UnaryExpression)result).Operand;

        return resu<
    }
}
  

Использование:

 myProducts.OrderBy(expr.AsConvertable());
  

Комментарии:

1. Почему «OrderBy» возвращается с IQueryable вместо ожидаемого IOrderedQueryable, как это делает «оригинальный» OrderBy? Должен ли я привести его к этому интерфейсу в конце?

2. Вы правы. Я изменил метод на return IOrderedQueryable .

Ответ №2:

попробуйте это

 List<Func<Products, Object>> list = new List<Func<Products, Object>>()
{
    new Func<Products,Object>( p => p.Name),
    new Func<Products,Object>( p => p.Id),
};
  

Комментарии:

1. Теперь нам нужно только объяснение, почему это работает как Expression со строкой, но не с типом int32, и почему ваше понимание отличается внутренне

2. Не вижу разницы между вашим решением и моим… какова основная точка зрения?

3. Это решение вызовет Linq для объектов ( IEnumerable.OrderBy ) вместо Linq для сущностей ( IQueryable.OrderBy ) и не будет преобразовано в store-query (в данном случае SQL).

Ответ №3:

Таким образом, похоже, что реализация OrderBy для EF работает, проверяя, является ли < T > структурой или объектом, и поэтому вы указываете ему вызвать OrderBy<…, object >(someStructTypeVariable)

В качестве обходного пути я бы предложил вам хранить целые делегаты вместо выражений.

Попробуйте это:

 internal static class MyExtensions
{
    public static IOrderedQueryable<TSource> OrderBy<TSource, TField>(this IQueryable<TSource> source, Expression<Func<TSource, TField>> selector, bool descending)
    {
        return descending 
            ? source.OrderByDescending(selector)
            : source.OrderBy(selector);
    }

}


var orderers = new List<Func<IQueryable<Products>, IOrderedQueryable<Products>>>()
    {
        source => source.OrderBy(x => x.Id, true),
        source => source.OrderBy(x => x.Id, false), 
        source => source.OrderBy(x => x.Name, false)
    };


// To be replaced with entity source-collection.
IQueryable<Products> dummySource = new EnumerableQuery<MyType>(new List<Products>());

orderers[0](dummySource.Where(x => x.Id != 0));
  

Комментарии:

1. Извините, для меня это не очень хорошее решение:( поскольку мои выражения могут использоваться либо в OrderBy, либо в OrderByDescending, ThenBy, ThenByDescending также.

2. Затем напишите метод-оболочку, который будет возвращать orderby или orderby по убыванию на основе дополнительного параметра. Проявите творческий подход.

3. Пытаюсь проявить творческий подход, но не могу понять, как ваше решение работает с методом-оболочкой — поскольку ваше решение напрямую вызывает OrderBy.

4. Я обновил пример для вас. Проверьте, работает ли это для вас. Но я действительно не знаю, зачем вы это используете. вы могли бы создать две коллекции: для возрастания и убывания.

5. Единственным другим решением было бы получить правильный метод путем отражения.