6

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

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

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 Entity Framework 生成 EntityContext 的方式进行比较,它允许为其他开发人员提供扩展点。因此,我想尝试使其健壮,并且在ProvideDefaultSorting未正确实施时不会崩溃。

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

更新 2
新问题已得到回答并被接受,我想我应该更改标题以更匹配问题。或者我应该留下当前的标题,因为它会引导有同样问题的人使用这个解决方案?

4

6 回答 6

2

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

支持对象

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));
于 2008-10-22T13:36:35.707 回答
1

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

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

于 2008-10-22T13:10:03.733 回答
1

您可以在 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);
    }
于 2008-10-22T17:02:17.180 回答
1

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

并且当分部方法没有实现或者只返回它的参数时,查询不会改变,这将使用回退排序。

于 2008-10-22T19:13:31.097 回答
0
    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]
于 2008-10-22T15:39:33.510 回答
0

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

请参阅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 错误修复。)

于 2011-05-19T17:19:46.330 回答