我有一个分页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,所以我可以在它进入SQL服务器之前操作IQueryable。
我的问题是,如果我已经有一个带有OrderBy的IQueryable,是否可以在调用Count()之前删除该OrderBy?
like:totalRecordCount = queryable。 NoOrder .Count();
如果没有,没有大事。我看到很多关于OrderBy的问题,但没有涉及从Linq表达式中删除OrderBy的任何问题。
谢谢!
答案 0 :(得分:7)
因此,下面的代码是针对内存数组的尖峰。使用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]);
}
}
答案 1 :(得分: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)
我认为你的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();
答案 2 :(得分:3)
我担心没有简单的方法可以从查询中删除OrderBy
运算符。
但是,您可以执行的操作是根据从重写IQueryable
(see here)中省略queryable.Expression
调用获得的新表达式重新创建OrderBy
。
答案 3 :(得分:2)
如果无法消除根本原因,可以采用以下解决方法:
totalRecordCount = queryable.OrderBy(x => 0).Count();
SQL Server的查询优化器将删除此无用的排序。它不会有运行时成本。
答案 4 :(得分: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 ...;
}
完成过滤和排序
答案 5 :(得分:0)
我知道这不是你想要的,但包含DataTimeStamp的[DataOwnerID]索引可以让你的查询更便宜。