Как проверить наличие OrderBy в дереве выражений ObjectQuery‹T›

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

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

System.NotSupportedException: метод «Пропустить» поддерживается только для отсортированного ввода в LINQ to Entities. Метод OrderBy должен вызываться перед методом 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 присутствует в дереве выражений? Единственный вариант разобрать дерево?

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

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

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

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


person Davy Landman    schedule 22.10.2008    source источник
comment
Вы должны увидеть этот ответ stackoverflow.com/questions/36923850/   -  person yosbel    schedule 28.04.2016


Ответы (6)


Вы можете решить эту проблему в возвращаемом типе 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);
    }
person Amy B    schedule 22.10.2008
comment
Спасибо за ваше время, это решило одну проблему: IF реализован частичный метод, я могу, по крайней мере, быть уверен, что он отсортирован. Тогда остается единственный случай, как посмотреть, реализован ли разделяемый метод или нет. - person Davy Landman; 22.10.2008
comment
Не нужно - просто выполните сортировку идентификаторов, а затем передайте IOrderedQueryable частичному методу. - person Amy B; 23.10.2008
comment
Это может быть возможно, но я бы хотел, чтобы запрос был как можно более чистым. - person Davy Landman; 23.10.2008
comment
Я принял это как ответ, хотя это частичный ответ, который помог мне завершить всю картину (я разместил это решение ниже). - person Davy Landman; 24.10.2008

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

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

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));
person Amy B    schedule 22.10.2008
comment
Хотя это хороший способ предложить параметр сортировки, мой интерфейс уже включает перегрузку, обеспечивающую сортировку. Я изменю свой вопрос, чтобы сделать его более понятным. - person Davy Landman; 22.10.2008

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

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

person Craig Stuntz    schedule 22.10.2008
comment
ладно, это облом. Итак, если я правильно понимаю, вы перемещаете логику о том, отсортировано это или нет, на уровень представления? Не могу сказать, что это звучит как идеальное решение. - person Davy Landman; 22.10.2008
comment
Поскольку мы позволяем пользователям динамически перебирать данные, сортировка на уровне представления имеет для нас смысл. Но я не буду утверждать, что это подходит для всех приложений. Делайте это там, где это имеет для вас смысл, но убедитесь, что это последнее, что вы делаете перед «Взять/пропустить», и что это делается только один раз. - person Craig Stuntz; 22.10.2008
comment
Я понимаю, что вы разрешаете пользователям динамически обрабатывать ваши данные (например, gridview). Один из моих перегруженных методов List имеет параметр String sortExpression, который анализируется и преобразуется в выражение LINQ. Возможно, проблема в том, как обеспечить сортировку по умолчанию. - person Davy Landman; 22.10.2008
comment
У нас есть репозиторий, указывающий выражение сортировки по умолчанию. Если пользователь не предоставляет собственное выражение сортировки, уровень представления может использовать значение по умолчанию из репозитория. Помните, что сортировка требуется только в том случае, если будет Take/Skip. Мы не хотим требовать этого иначе. - person Craig Stuntz; 22.10.2008
comment
Я проголосовал за него, хотя это не ответ, но я думаю, важно отметить, что OrderBy игнорируется, когда он находится не в нужном месте (когда вы думаете о его логичности). - person Davy Landman; 24.10.2008

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

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.

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

person Davy Landman    schedule 22.10.2008

    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]
person Amy B    schedule 22.10.2008
comment
Это действительно сработает, и на данный момент у меня есть это как временное решение (чтобы не сломать сборку). Но это не то решение, которое я искал. - person Davy Landman; 22.10.2008

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

См. 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.)

person John Kaster    schedule 19.05.2011