如何检查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的附带错误修复。)