如何检查ObjectQuery 表达式树中是否存在OrderBy

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

存储库包含(除其他外)适合于分页的List方法。 支持和不支持的方法的文档没有提到它,但你无法“调用” Skip无序的IQueryable 。 它会引发以下exception:

System.NotSupportedException:方法’Skip’仅支持LINQ to Entities中的排序输入。 必须在方法’Skip’之前调用’OrderBy’方法。

我通过允许通过部分方法定义默认排序来解决它。 但是我在检查表达式树是否确实包含OrderBy遇到了问题。

我已经将问题减少到尽可能少的代码:

 public partial class Repository { partial void ProvideDefaultSorting(ref IQueryable currentQuery); public IQueryable List(int startIndex, int count) { IQueryable query = List(); ProvideDefaultSorting(ref query); if (!IsSorted(query)) { query = query.OrderBy(c => c.CategoryID); } return query.Skip(startIndex).Take(count); } public IQueryable List(string sortExpression, int startIndex, int count) { return List(sortExpression).Skip(startIndex).Take(count); } public IQueryable List(string sortExpression) { return AddSortingToTheExpressionTree(List(), sortExpression); } public IQueryable List() { NorthwindEntities ent = new NorthwindEntities(); return ent.Categories; } private Boolean IsSorted(IQueryable query) { return query is IOrderedQueryable; } } public partial class Repository { partial void ProvideDefaultSorting(ref IQueryable currentQuery) { currentQuery = currentQuery.Where(c => c.CategoryName.Contains(" ")); // no sorting.. } } 

这不是我真正的实施!

但我的问题是,我怎样才能实现IsSorted方法? 问题是LINQ to Entities查询始终是ObjectQuery类型,它实现了IOrderedQueryable

那么我应该如何确保表达式树中存在OrderBy方法? 是解析树的唯一选择吗?

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

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

所以问题可能更多,我怎样才能确认ProvideDefaultSorting确实在表达式树中添加了排序。

更新2
新问题得到了回答,并被接受,我想我应该更改标题以更多地匹配问题。 或者我应该保留当前的标题,因为它会引导人们遇到同样的问题吗?

您可以在ProvideDefaultSorting的返回类型中解决此问题。 此代码不构建:

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

这段代码构建,但是阴险,编码器得到他们应得的。

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

与ref相同的故事(这不构建):

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

分页取决于强有力的排序。 为什么不紧密结合运营? 这是一种方法:

支持对象

 public interface IOrderByExpression { ApplyOrdering(ref IQueryable query); } public class OrderByExpression : IOrderByExpression { public IQueryable ApplyOrderBy(ref IQueryable query) { query = query.OrderBy(exp); } //TODO OrderByDescending, ThenBy, ThenByDescending methods. private Expression> exp = null; //TODO bool descending? public OrderByExpression (Expression> myExpression) { exp = myExpression; } } 

正在讨论的方法:

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

一段时间后,调用方法:

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

我担心这比那更难。 您会看到,在某些情况下,entity framework会默默地忽略OrderBy。 因此仅仅在表达式树中查找OrderBy是不够的。 OrderBy必须位于“正确”的位置,“正确”位置的定义是entity framework的实现细节。

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

感谢David B,我得到了以下解决方案。 (我必须为没有执行部分方法的情况添加检测,或者仅返回它的参数)。

 public partial class Repository { partial void ProvideDefaultSorting(ref IOrderedQueryable currentQuery); public IQueryable List(int startIndex, int count) { NorthwindEntities ent = new NorthwindEntities(); IOrderedQueryable 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 currentQuery) { currentQuery = currentQuery.Where(c => c.CategoryName.Contains(" ")).OrderBy(c => c.CategoryName); // compile time forced sotring } } 

它确保在编译时如果实现部分方法,它至少应该保持IOrderdQueryable。

并且当未实现partial方法或仅返回其参数时,查询将不会更改,并且将使用回退排序。

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

这不是一个完美的解决方案。

它没有解决您所说的“订购function中的filter”问题。 它确实解决了“我忘了实施订购”或“我选择不订购”。

我在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的附带错误修复。)