我有一个复杂的实体框架查询。我的性能瓶颈实际上并不是查询数据库,而是将IQueryable转换为查询文本。
我的代码是这样的:
var query = context.Hands.Where(...)
if(x)
query = query.where(...)
....
var result = query.OrderBy(...)
var page = result.skip(500 * pageNumber).Take(500).ToList(); //loong time here, even before calling the DB
do
{
foreach(var h in page) { ... }
pageNumber += 1;
page = result.skip(500 * pageNumber).Take(500).ToList(); //same here
}
while(y)
我该怎么办?我正在使用DbContext(使用SQLite),因此我无法使用预编译查询(即便如此,使用这样的查询构建算法也会很麻烦。)
我基本上需要的是缓存“页面”查询并仅更改“跳过”和“获取”参数,而不是每次都从头开始重新编译。
答案 0 :(得分:4)
你的前提是不正确的。因为您在查询结束时进行了ToList
调用,所以 查询您指定的数据库,以构建列表。你不再推迟执行。这就是为什么需要这么长时间。你没有花很长时间构建查询,它需要很长时间才能进入数据库并实际执行它。
如果有帮助,您可以使用以下方法为您进行分页。它将推迟获取每个页面,直到您要求下一页:
public static IEnumerable<IEnumerable<T>> Paginate<T>(
this IQueryable<T> query, int pagesize)
{
int pageNumber = 0;
var page = query.Take(pagesize).ToList();
while (page.Any())
{
yield return page;
pageNumber++;
page = query.Skip(pageNumber * pagesize)
.Take(pagesize)
.ToList();
}
}
所以如果你有这个代码:
var result = query.OrderBy(...);
var pages = result.Paginate();//still haven't hit the database
//each iteration of this loop will go query the DB once to get that page
foreach(var page in pages)
{
//use page
}
如果你想获得一个IEnumerable<IQueryable<T>>
,其中你将所有页面都作为查询(意味着你可以在将它们发送到数据库之前为它们添加更多过滤器),那么你遇到的主要问题是你没有我不知道会有多少页。您需要实际执行给定查询以确定它是否是最后一页。您可能需要像往常一样获取每个页面,或者您需要在开始时查询未分页查询的计数(这意味着比您需要的更多数据库查询)。这样做会是这样的:
public static IEnumerable<IQueryable<T>> Paginate<T>(
this IQueryable<T> query, int pagesize)
{
//note that this is hitting the DB
int numPages = (int)Math.Ceiling(query.Count() / (double)pagesize);
for (int i = 0; i < numPages; i++)
{
var page = query.Skip(i * pagesize)
.Take(pagesize);
yield return page;
}
}