Как проверить наличие OrderBy в дереве выражений ObjectQuery <T> - PullRequest
6 голосов
/ 22 октября 2008

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

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

System.NotSupportedException: метод «Пропустить» поддерживается только для отсортированный ввод в LINQ to Entities. Метод OrderBy должен быть вызван до метод "Пропустить" ..

Я решил это, позволив определить сортировку по умолчанию с помощью частичного метода. Но у меня возникают проблемы с проверкой, действительно ли дерево выражений содержит 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
На новый вопрос ответили и приняли, я думаю, что я должен изменить название, чтобы оно больше соответствовало вопросу. Или я должен оставить текущее название, потому что это приведет людей с такой же проблемой к этому решению?

Ответы [ 6 ]

2 голосов
/ 22 октября 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));
1 голос
/ 22 октября 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.

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

1 голос
/ 22 октября 2008

Вы можете решить эту проблему в типе возврата 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);
    }
1 голос
/ 22 октября 2008

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

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

0 голосов
/ 19 мая 2011

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

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

0 голосов
/ 22 октября 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]
...