如何在ObjectQuery中检查OrderBy的存在<;T>;表达式树

本文关键字:lt gt 表达式 存在 ObjectQuery OrderBy 检查 | 更新日期: 2023-09-27 17:48:51

我使用T4为LINQ to Entities实体生成存储库。

存储库包含(除其他外)一个适用于分页的List方法。支持的方法和不支持的方法的文档没有提到它,但您不能在无序的IQueryable上"调用"Skip。它将引发以下异常:

System.NotSupportedException:只支持方法"Skip"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方法呢?解析树的唯一选项是什么?

更新
我添加了另外两个重载,以表明这不是关于如何向存储库添加排序支持,而是关于如何检查ProvideDefaultSorting分部方法是否确实向表达式树添加了OrderBy

问题是,第一个分部类是由模板生成的,分部类的第二部分是由团队成员在其他时间实现的。您可以将其与.NET实体框架生成EntityContext的方式进行比较,它允许其他开发人员使用扩展点。因此,我想尝试使其健壮,并且在ProvideDefaultSorting未正确实现时不会崩溃。

因此,问题可能更多,我如何确认ProvideDefaultSorting确实向表达式树添加了排序。

更新2
新的问题已经回答并被接受了,我想我应该更改标题以更符合这个问题。或者我应该离开目前的标题,因为它会把有同样问题的人引向这个解决方案?

如何在ObjectQuery中检查OrderBy的存在<;T>;表达式树

分页在很大程度上取决于排序。为什么不把手术紧密结合起来呢?这里有一种方法:

支持对象

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

您可以在ProviderDefaultSorting的返回类型中解决此问题。此代码不生成:

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

与参考文献相同的故事(这并不成立):

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

恐怕比这更难。您可以看到,在某些情况下,实体框架会默默地忽略OrderBy。因此,仅仅在表达式树中查找OrderBy是不够的。OrderBy必须位于"正确"的位置,"正确"位置的定义是实体框架的实现细节。

正如你现在可能已经猜到的,我和你在同一个地方;我正在使用实体存储库模式,并在表示层上执行Take/Skip操作。我使用的解决方案可能并不理想,但对于我正在做的事情来说已经足够好了,它是在最后一刻之前不进行任何排序,以确保OrderBy始终是表达式树中的最后一个元素。因此,任何要执行Take/Skip(直接或间接)的操作都会首先插入OrderBy。代码的结构使得这种情况只能发生一次。

感谢David B,我得到了以下解决方案。(对于分部方法没有执行或只是返回其参数的情况,我不得不添加检测)。

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。

当分部方法没有实现或只是返回其参数时,查询将不会更改,并且将使用回退排序。

    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]

我已经实现了一个解决方案,它根据主键对任何集合进行排序,因为没有指定默认的排序顺序。也许这对你有用。

请参阅http://johnkaster.wordpress.com/2011/05/19/a-bug-fix-for-system-linq-dynamic-and-a-solution-for-the-entity-framework-4-skip-problem/用于讨论和通用代码。(以及动态LINQ的附带错误修复。)