缓存/编译复杂的Linq查询(实体框架)

时间:2013-07-03 14:37:09

标签: c# linq entity-framework

我有一个复杂的实体框架查询。我的性能瓶颈实际上并不是查询数据库,而是将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),因此我无法使用预编译查询(即便如此,使用这样的查询构建算法也会很麻烦。)

我基本上需要的是缓存“页面”查询并仅更改“跳过”和“获取”参数,而不是每次都从头开始重新编译。

1 个答案:

答案 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;
    }
}