12

我有一个分页 API,它返回用户请求的行,但一次只能返回这么多行,而不是整个集合。API 按设计工作,但我必须计算可用记录的总数(用于正确的页面计算)。在 API 中,我使用 Linq2Sql,并且在我最终提出请求之前,我使用 IQueryable 做了很多工作。当我去计数时,我会调用类似:totalRecordCount = queryable.Count();

生成的 SQL 仍然很有趣,但它也添加了不必要的 Order By,这使得查询非常昂贵。

exec sp_executesql N'SELECT COUNT(*) AS [value]
FROM (
    SELECT TOP (1) NULL AS [EMPTY]
    FROM [dbo].[JournalEventsView] AS [t0]
    WHERE [t0].[DataOwnerID] = @p0
    ORDER BY [t0].[DataTimeStamp] DESC
    ) AS [t1]',N'@p0 int',@p0=1

因为我使用的是 IQueryable,所以我可以在 IQueryable 进入 SQL 服务器之前对其进行操作。

我的问题是,如果我已经有一个带有 OrderBy 的 IQueryable,是否可以在调用 Count() 之前删除该 OrderBy?

比如:totalRecordCount = 可查询。NoOrder .Count();

如果没有,没什么大不了的。我看到很多关于如何 OrderBy 的问题,但没有任何涉及从 Linq 表达式中删除 OrderBy 的问题。

谢谢!

4

6 回答 6

9

因此,下面的代码是针对内存数组的尖峰。使用 Entity Framework(或其他一些任意的 IQueryProvider 实现)可能会遇到一些障碍。基本上,我们要做的是访问表达式树并查找任何 Ordering 方法调用,然后将其从树中删除。希望这能为您指明正确的方向。

class Program
{
    static void Main(string[] args)
    {
        var seq = new[] { 1, 3, 5, 7, 9, 2, 4, 6, 8 };

        var query = seq.OrderBy(x => x);

        Console.WriteLine("Print out in reverse order.");
        foreach (var item in query)
        {
            Console.WriteLine(item);
        }

        Console.WriteLine("Prints out in original order");
        var queryExpression = seq.AsQueryable().OrderBy(x => x).ThenByDescending(x => x).Expression;

        var queryDelegate = Expression.Lambda<Func<IEnumerable<int>>>(new OrderByRemover().Visit(queryExpression)).Compile();

        foreach (var item in queryDelegate())
        {
            Console.WriteLine(item);
        }


        Console.ReadLine();
    }
}

public class OrderByRemover : ExpressionVisitor
{
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Method.DeclaringType != typeof(Enumerable) && node.Method.DeclaringType != typeof(Queryable))
            return base.VisitMethodCall(node);

        if (node.Method.Name != "OrderBy" && node.Method.Name != "OrderByDescending" && node.Method.Name != "ThenBy" && node.Method.Name != "ThenByDescending")
            return base.VisitMethodCall(node);

        //eliminate the method call from the expression tree by returning the object of the call.
        return base.Visit(node.Arguments[0]);
    }
}
于 2012-05-14T23:41:30.953 回答
6

不仅有一个不需要的 ORDER BY,还有一个虚假的 TOP(1)。

SELECT TOP (1) NULL AS [EMPTY] ...

该子选择将仅返回 0 或 1 行。事实上,如果没有 TOP,在子选择中使用 ORDER BY 是不合法的。

ORDER BY 子句在视图、内联函数、派生表、子查询和公用表表达式中无效,除非还指定了 TOP 或 FOR XML。:SELECT COUNT(*) FROM (SELECT * FROM Table1 ORDER BY foo)

sqlfiddle

我认为您可能在 LINQ 中做错了什么。.Take(1)在致电之前,您确定您没有在查询中的某处写过或类似的信息.Count()吗?

这是错误的:

IQueryable<Foo> foo = (...).OrderBy(x => x.Foo).Take(1);
int count = foo.Count();

你应该这样做:

IQueryable<Foo> foo = (...);
Iqueryable<Foo> topOne = foo.OrderBy(x => x.Foo).Take(1);
int count = foo.Count();
于 2012-05-14T21:05:39.613 回答
3

恐怕没有简单的方法可以OrderBy从可查询中删除运算符。

但是,您可以做的是IQueryable根据从重写queryable.Expression参见此处)获得的新表达式重新创建省略OrderBy调用。

于 2012-05-14T21:24:30.393 回答
2

如果您无法消除根本原因,这里有一个解决方法:

totalRecordCount = queryable.OrderBy(x => 0).Count();

SQL Server 的查询优化器将删除这种无用的排序。它不会有运行时成本。

于 2012-05-14T21:16:34.910 回答
0

我认为您错误地实现了分页代码。您实际上需要查询数据库两次,一次用于分页数据源,一次用于总行数。这就是设置的外观。

public IList<MyObj> GetPagedData(string filter, string sort, int skip, int take)
{
   using(var db = new DataContext())
   {
      var q = GetDataInternal(db);
      if(!String.IsNullOrEmpty(filter))
         q = q.Where(filter); //Using Dynamic linq

      if(!String.IsNullOrEmpty(sort))
         q = q.OrderBy(sort); //And here

      return q.Skip(skip).Take(take).ToList();
   }
}

public int GetTotalCount(string filter)
{
    using(var db = new DataContext())
    {
       var q = GetDataInternal(db);
       if(!String.IsNullOrEmpty(filter))
         q = q.Where(filter); //Using Dynamic linq

       return q.Count(); //Without ordering and paging.
    }
}

private static IQuerable<MyObj> GetDataInternal(DataContext db)
{
   return 
        from x in db.JournalEventsView 
        where ...
        select new ...;
}

过滤和排序是使用动态 linq 库完成的

于 2012-05-14T21:55:50.233 回答
0

我知道这不是您要查找的内容,但是包含 DataTimeStamp 的 [DataOwnerID] 上的索引可以使您的查询成本更低。

于 2012-05-15T01:32:46.537 回答