代码之家  ›  专栏  ›  技术社区  ›  Davy Landman

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

  •  6
  • Davy Landman  · 技术社区  · 16 年前

    我使用t4为linq-to-entities实体生成存储库。

    存储库包含(除其他外)适合分页的列表方法。文件 Supported and Unsupported Methods 没说,但你不能打电话 Skip 无序的 IQueryable . 它将引发以下异常:

    System.NotSupportedException:方法“Skip”仅支持 已将LINQ中的输入排序为实体。必须先调用方法“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 .

    那么我应该如何确保 排序 方法是否存在于表达式树中?是解析树的唯一选项吗?

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

    问题是,第一个分部类是由模板生成的,分部类第二部分的实现是由团队成员在另一个时间完成的。您可以将其与.NET实体框架生成EntityContext的方式进行比较,它允许其他开发人员使用扩展点。所以我想努力使它健壮,而不是崩溃时 提供默认排序 未正确实现。

    所以也许问题更多,我如何才能确认 提供默认排序 确实向表达式树添加了排序。

    更新2
    新问题得到了回答并被接受,我想我应该更改标题以更匹配问题。还是应该保留当前的标题,因为它将引导具有相同问题的人员使用此解决方案?

    6 回复  |  直到 9 年前
        1
  •  1
  •   Amy B    16 年前

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

        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);
        }
    
        2
  •  2
  •   Amy B    16 年前

    分页依赖于强大的排序方式。为什么不把操作紧密地结合起来呢?有一种方法可以做到:

    支持对象

    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));
    
        3
  •  1
  •   Craig Stuntz    16 年前

    恐怕有点难。你看,实体框架在某些情况下, silently ignore an OrderBy. 所以仅仅在表达式树中查找orderby是不够的。orderby必须位于“正确”的位置,而“正确”位置的定义是实体框架的实现细节。

    正如您现在可能已经猜到的,我和您处于同一个位置;我正在使用实体存储库模式,并在表示层上执行take/skip。我使用的解决方案可能不理想,但足以满足我所做的工作,就是在最后可能的时刻之前不进行任何排序,以确保orderby始终是表达式树中的最后一件事情。因此,要执行take/skip(直接或间接)的任何操作都首先插入orderby。代码的结构使得这种情况只能发生一次。

        4
  •  1
  •   Davy Landman    16 年前

    多亏了大卫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
        }
    }
    

    它确保在编译时,如果实现了分部方法,则至少应将其保持为iorddqueryable。

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

        5
  •  0
  •   Amy B    16 年前
        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
  •  0
  •   John Kaster    14 年前

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

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