【问题标题】:How to check for the presence of an OrderBy in a ObjectQuery<T> expression tree如何检查 ObjectQuery<T> 表达式树中是否存在 OrderBy
【发布时间】:2008-10-22 12:06:33
【问题描述】:

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

存储库包含(除其他外)一个适合分页的 List 方法。 Supported and Unsupported Methods 的文档没有提到它,但你不能在无序的 IQueryable 上“调用”Skip。它将引发以下异常:

System.NotSupportedException:方法“Skip”仅支持 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
新问题已得到回答并被接受,我认为我应该更改标题以更匹配问题。还是我应该保留当前标题,因为它会引导有相同问题的人使用此解决方案?

【问题讨论】:

标签: c# linq generics linq-to-entities code-generation


【解决方案1】:

分页强烈依赖于排序。为什么不将操作紧密耦合?这是一种方法:

支持对象

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

【讨论】:

  • 虽然这是提供排序参数的好方法,但我的界面已经包含一个提供排序的重载。我将更改我的问题以使其更清楚。
【解决方案2】:

您可以在 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);
    }

【讨论】:

  • 感谢您的宝贵时间,这确实解决了一个问题,如果实现了部分方法,我至少可以确定它已排序。那么就剩下唯一的情况了,怎么看分部方法有没有实现。
  • 不需要 - 只需进行 ID 排序,然后将 IOrderedQueryable 交给部分方法。
  • 这可能是一种可能性,但我希望尽可能保持查询干净。
  • 我已经接受了这个作为答案,虽然这是部分答案,但它确实帮助我完成了整个画面(我在下面发布了该解决方案)。
【解决方案3】:

恐怕比那更难。您会看到,在某些情况下,实体框架将 silently ignore an OrderBy. 因此仅在表达式树中查找 OrderBy 是不够的。 OrderBy 必须在“正确”的地方,而“正确”的地方的定义是实体框架的一个实现细节。

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

【讨论】:

  • 好吧,那真是太糟糕了。因此,如果我理解正确,您将有关是否已排序的逻辑移至表示层?我不能说这听起来像是一个理想的解决方案。
  • 因为我们允许用户动态地使用数据,所以在表示层上进行排序对我们来说很有意义。但我不会声称它适用于所有应用程序。在对你有意义的地方做,但要确保这是你在 Take/Skip 之前做的最后一件事,而且只做一次。
  • 我了解您允许用户动态地使用您的数据(例如 gridview)。我的重载 List 方法之一有一个 String sortExpression 参数,该参数被解析并转换为 LINQ 表达式。也许问题实际上是如何强制执行默认排序。
  • 我们让存储库指定一个默认的排序表达式。如果用户不提供自己的排序表达式,表示层可以使用存储库中的默认值。请记住,仅当存在 Take/Skip 时才需要排序。否则我们不想要求它。
  • 我赞成它,虽然它不是一个答案,但我认为重要的是要注意 OrderBy 在不正确的地方被忽略(当你认为它有点合乎逻辑时)。
【解决方案4】:

感谢 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。

并且当partial方法没有实现或者只是返回它的参数时,查询不会改变,并且会使用fallback排序。

【讨论】:

    【解决方案5】:
        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]
    

    【讨论】:

    • 这确实可行,目前我确实将其作为临时解决方案(不破坏构建)。但这不是我想要的那种解决方案。
    【解决方案6】:

    我已经实现了一个解决方案,该解决方案通过其主键对任何集合进行排序,因为未指定默认排序顺序。也许这对你有用。

    有关讨论和通用代码,请参阅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 错误修复。)

    【讨论】:

      猜你喜欢
      • 2019-07-02
      • 2016-04-17
      • 2014-05-25
      • 1970-01-01
      • 1970-01-01
      • 2020-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多