Как проверить наличие OrderBy в дереве выражения ObjectQuery <T>

Я использую T4 для создания репозиториев для сущностей LINQ to Entities.

Репозиторий содержит (среди прочего) метод List, подходящий для разбиения на страницы. В документации для Поддерживаемые и неподдерживаемые методы это не упоминается, но вы не можете "вызвать" Skip на неупорядоченном IQueryable. Это вызовет следующее исключение:

System.NotSupportedException: The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip'..

Я решил это, позволив определить сортировку по умолчанию с помощью частичного метода. Но у меня проблемы с проверкой, действительно ли дерево выражения содержит OrderBy.

Я сократил проблему до как можно меньшего количества кода:

public partial class Repository
{
    partial void ProvideDefaultSorting(ref IQueryable<Category> currentQuery);

    public IQueryable<Category> List(int startIndex, int count)
    {
        IQueryable<Category> query = List();
        ProvideDefaultSorting(ref query);
        if (!IsSorted(query))
        {
            query = query.OrderBy(c => c.CategoryID);
        }
        return query.Skip(startIndex).Take(count);
    }
    public IQueryable<Category> List(string sortExpression, int startIndex, int count)
    {
           return List(sortExpression).Skip(startIndex).Take(count);
    }
    public IQueryable<Category> List(string sortExpression)
    {
        return AddSortingToTheExpressionTree(List(), sortExpression);
    }
    public IQueryable<Category> List()
    {
           NorthwindEntities ent = new NorthwindEntities();
           return ent.Categories;
    }

    private Boolean IsSorted(IQueryable<Category> query)
    {
        return query is IOrderedQueryable<Category>; 
    }
}

public partial class Repository
{
    partial void ProvideDefaultSorting(ref IQueryable<Category> currentQuery)
    {
        currentQuery = currentQuery.Where(c => c.CategoryName.Contains(" ")); // no sorting..
    }
}

Это не моя настоящая реализация!

Но мой вопрос - как я могу реализовать метод IsSorted? Проблема в том, что запросы LINQ to Entities всегда относятся к типу ObjectQuery, который реализует IOrderedQueryable.

Итак, как мне убедиться, что метод OrderBy присутствует в дереве выражений? Единственный вариант - разобрать дерево?

Обновлять
Я добавил две другие перегрузки, чтобы прояснить, что речь идет не о том, как добавить поддержку сортировки в репозиторий, а о том, как проверить, действительно ли частичный метод ProvideDefaultSorting добавил OrderBy в дерево выражений.

Проблема в том, что первый частичный класс генерируется шаблоном, а реализация второй части частичного класса выполняется членом команды в другое время. Вы можете сравнить это с тем, как .NET Entity Framework генерирует EntityContext, он позволяет использовать точки расширения для других разработчиков. Поэтому я хочу попытаться сделать его надежным и не давать сбоев при неправильной реализации ProvideDefaultSorting.

Так что, возможно, вопрос в другом: как я могу подтвердить, что ProvideDefaultSorting действительно добавил сортировку в дерево выражений.

Обновление 2
На новый вопрос был дан ответ, и он был принят, я думаю, мне следует изменить заголовок, чтобы он больше соответствовал вопросу. Или мне следует оставить текущее название, потому что оно приведет к этому решению людей с той же проблемой?

Вы должны увидеть этот ответ stackoverflow.com/questions/36923850/…

yosbel 28.04.2016 23:24
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
1
6 734
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Боюсь, это немного сложнее. Видите ли, Entity Framework при определенных обстоятельствах будет молча игнорировать OrderBy., поэтому недостаточно просто искать OrderBy в дереве выражений. OrderBy должен находиться в «правильном» месте, а определение «правильного» места является деталью реализации Entity Framework.

Как вы уже догадались, я нахожусь в том же месте, что и вы; Я использую шаблон репозитория сущностей и делаю Take / Skip на уровне представления. Решение, которое я использовал, которое, возможно, не идеально, но достаточно хорошо для того, что я делаю, состоит в том, чтобы не выполнять никаких заказов до последнего возможного момента, чтобы гарантировать, что OrderBy всегда будет последним элементом в дереве выражений. Таким образом, любое действие, которое будет выполнять Take / Skip (прямо или косвенно), сначала вставляет OrderBy. Код структурирован так, что это может произойти только один раз.

хорошо, это облом. Итак, если я правильно понимаю, вы перемещаете логику о том, отсортировано ли оно или нет, на уровень представления? Не могу сказать, что это звучит как идеальное решение.

Davy Landman 22.10.2008 17:20

Поскольку мы позволяем пользователям динамически преобразовывать данные, сортировка на уровне представления имеет для нас смысл. Но я не буду утверждать, что это подходит для всех приложений. Делайте это там, где это имеет смысл, но убедитесь, что это последнее, что вы делаете перед Take / Skip, и это делается только один раз.

Craig Stuntz 22.10.2008 17:41

Я понимаю, что вы разрешаете пользователям динамически использовать ваши данные (например, gridview). Один из моих перегруженных методов List имеет параметр String sortExpression, который анализируется и преобразуется в выражение LINQ. Возможно, проблема на самом деле заключается в том, как применить сортировку по умолчанию.

Davy Landman 22.10.2008 18:01

У нас есть репозиторий, определяющий выражение сортировки по умолчанию. Если пользователь не предоставляет собственное выражение сортировки, уровень представления может использовать значение по умолчанию из репозитория. Помните, что сортировка требуется только в том случае, если будет Take / Skip. Иначе мы не хотим этого требовать.

Craig Stuntz 22.10.2008 18:27

Я поддержал его, хотя это не ответ, но я думаю, что важно отметить, что OrderBy игнорируется, когда находится не в правильном месте (когда вы думаете о его логичном).

Davy Landman 24.10.2008 10:46

Пейджинг во многом зависит от порядка. Почему бы не объединить операции плотно? Вот один из способов сделать это:

Объекты поддержки

public interface IOrderByExpression<T>
{
  ApplyOrdering(ref IQueryable<T> query);
}

public class OrderByExpression<T, U> : IOrderByExpression<T>
{
  public IQueryable<T> ApplyOrderBy(ref IQueryable<T> query)
  {
    query = query.OrderBy(exp);
  }
  //TODO OrderByDescending, ThenBy, ThenByDescending methods.

  private Expression<Func<T, U>> exp = null;

  //TODO bool descending?
  public OrderByExpression (Expression<Func<T, U>> myExpression)
  {
    exp = myExpression;
  }
}

Обсуждаемый метод:

public IQueryable<Category> List(int startIndex, int count, IOrderByExpression<Category> ordering)
{
    NorthwindEntities ent = new NorthwindEntities();
    IQueryable<Category> query = ent.Categories;
    if (ordering == null)
    {
      ordering = new OrderByExpression<Category, int>(c => c.CategoryID)
    }
    ordering.ApplyOrdering(ref query);

    return query.Skip(startIndex).Take(count);
}

Некоторое время спустя вызов метода:

var query = List(20, 20, new OrderByExpression<Category, string>(c => c.CategoryName));

Хотя это хороший способ предложить параметр сортировки, мой интерфейс уже включает перегрузку, обеспечивающую сортировку. Я изменю свой вопрос, чтобы он был более ясным.

Davy Landman 22.10.2008 18:03
    ProvideDefaultSorting(ref query);
    if (!IsSorted(query))
    {
            query = query.OrderBy(c => c.CategoryID);
    }

Измените на:

    //apply a default ordering
    query = query.OrderBy(c => c.CategoryID);
    //add to the ordering
    ProvideDefaultSorting(ref query);

Это не идеальное решение.

Это не решает проблему "фильтра в функции упорядочивания", которую вы указали. Это решает проблему «Я забыл оформить заказ» или «Я решил не заказывать».

Я тестировал это решение в LinqToSql:

    public void OrderManyTimes()
    {
        DataClasses1DataContext myDC = new DataClasses1DataContext();
        var query = myDC.Customers.OrderBy(c => c.Field3);
        query = query.OrderBy(c => c.Field2);
        query = query.OrderBy(c => c.Field1);

        Console.WriteLine(myDC.GetCommand(query).CommandText);

    }

Генерирует (обратите внимание на обратный порядок следования):

SELECT Field1, Field2, Field3
FROM [dbo].[Customers] AS [t0]
ORDER BY [t0].[Field1], [t0].[Field2], [t0].[Field3]

Это действительно сработает, и на данный момент у меня есть это временное решение (чтобы не нарушить сборку). Но это не то решение, которое я искал.

Davy Landman 22.10.2008 20:25
Ответ принят как подходящий

Вы можете решить эту проблему в возвращаемом типе ProvideDefaultSorting. Этот код не строит:

    public IOrderedQueryable<int> GetOrderedQueryable()
    {
        IQueryable<int> myInts = new List<int>() { 3, 4, 1, 2 }.AsQueryable<int>();
        return myInts.Where(i => i == 2);
    }

Этот код строится, но коварен, и программист получает то, что заслуживает.

    public IOrderedQueryable<int> GetOrderedQueryable()
    {
        IQueryable<int> myInts = new List<int>() { 3, 4, 1, 2 }.AsQueryable<int>();
        return myInts.Where(i => i == 2) as IOrderedQueryable<int>;
    }

Та же история с ref (это не сборка):

    public void GetOrderedQueryable(ref IOrderedQueryable<int> query)
    {
        query = query.Where(i => i == 2);
    }

Спасибо за ваше время, это решило одну проблему, ЕСЛИ, частичный метод реализован, я, по крайней мере, могу быть уверен, что он отсортирован. Тогда остается единственный случай, как узнать, реализован ли частичный метод или нет.

Davy Landman 22.10.2008 23:08

В этом нет необходимости - просто выполните сортировку идентификаторов, а затем передайте IOrderedQueryable частичному методу.

Amy B 23.10.2008 00:04

Это могло быть возможным, но я хотел бы, чтобы запрос был как можно более чистым.

Davy Landman 23.10.2008 00:12

Я принял это как ответ, хотя это частичный ответ, который помог мне завершить всю картину (я разместил это решение ниже).

Davy Landman 24.10.2008 10:44

Благодаря Дэвиду Б. у меня есть следующее решение. (Мне пришлось добавить обнаружение ситуации, когда частичный метод не выполнялся или просто возвращал его параметр).

public partial class Repository
{
    partial void ProvideDefaultSorting(ref IOrderedQueryable<Category> currentQuery);

    public IQueryable<Category> List(int startIndex, int count)
    {
        NorthwindEntities ent = new NorthwindEntities();
        IOrderedQueryable<Category> query = ent.CategorySet;
        var oldQuery = query;
        ProvideDefaultSorting(ref query);
        if (oldQuery.Equals(query)) // the partial method did nothing with the query, or just didn't exist
        {
            query = query.OrderBy(c => c.CategoryID);
        }
        return query.Skip(startIndex).Take(count);
    }
    // the rest..        
}

public partial class Repository
{
    partial void ProvideDefaultSorting(ref IOrderedQueryable<Category> currentQuery)
    {
        currentQuery = currentQuery.Where(c => c.CategoryName.Contains(" ")).OrderBy(c => c.CategoryName); // compile time forced sotring
    }
}

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

И когда частичный метод не реализован или просто возвращает свой параметр, запрос не будет изменен, и будет использоваться резервная сортировка.

Я реализовал решение, которое сортирует любую коллекцию по ее первичному ключу, поскольку порядок сортировки по умолчанию не указан. Возможно, это сработает для вас.

См. Обсуждение и универсальный код в http://johnkaster.wordpress.com/2011/05/19/a-bug-fix-for-system-linq-dynamic-and-a-solution-for-the-entity-framework-4-skip-problem/. (И случайное исправление ошибки для Dynamic LINQ.)

Другие вопросы по теме