如何在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
新的问题已经回答并被接受了,我想我应该更改标题以更符合这个问题。或者我应该离开目前的标题,因为它会把有同样问题的人引向这个解决方案?
分页在很大程度上取决于排序。为什么不把手术紧密结合起来呢?这里有一种方法:
支持对象
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的附带错误修复。)