C#实体框架分页

22

有没有一种方法可以在不使数据库受到两次查询或编写2个单独的查询的情况下获取复杂Linq查询和数百万记录的行计数?

我可能有自己的建议。编写存储过程,但我擅长MySQL而不是MSSQL。

如果有更好的建议,那就太棒了。此外,如果有人知道Microsoft是否正在将此功能添加到实体框架中。


3
我很有信心地认为,在不访问数据库或编写另一个查询以返回行数的情况下,你无法获取查询结果的行数。 - Jonathan Wood
1
当您在EF中使用.Count()时,它不会选择所有行,它只执行一个select count() from table SQL语句 - 因此,虽然您确实需要2个查询,但其中一个查询非常便宜。 - JK.
@JK select count() 一点也不便宜!实际上,它的复杂度几乎与实际获取数据相同,唯一的区别是它只计算行数而不是获取行。但它仍然必须执行所有扫描等操作。 - Vladimir Perevalov
如果我有一个复杂的查询,只是计算结果,可能会有10,000个或更多,另一个查询只会获取其中的20个结果。Count()会对整个过程产生影响吗? - Jason Foglia
只是想提一下,我测试了计数和结果执行时间(以毫秒为单位),发现如下情况:总记录数:1,324,224,计数时间平均值:125,20个项目 - 结果时间平均值:2850。 - Jason Foglia
6个回答

18

我建议使用Take()函数。这个函数可以用来指定从LINQ查询或列表中获取的记录数。例如:

List<customers> _customers = (from a in db.customers select a).ToList();
var _dataToWebPage = _customers.Take(50);

在我的MVC应用程序中,我使用类似的技术将_customers列表写入会话,然后在用户点击第2页、第3页等时使用此列表进行进一步的分页查询。这样可以避免多次访问数据库。但是,如果您的列表非常大,则将其写入会话可能不是一个好主意。

对于分页,您可以同时使用Skip()和Take()函数。例如,要获取数据的第2页:

var _dataToWebPage = _customers.Skip(50).Take(50);

5
谢谢@TimNewton。当然,但如果我有100万条记录,或者更好的是有1000条记录,数据列非常大,则会遇到内存不足异常。 - Jason Foglia
Jason,你可以考虑将主键写入会话中的列表而不是整个对象,然后每次需要重新显示数据时,使用列表中的主键从数据库检索详细信息。但这仍然需要多次读取数据库。如果数据集如此之大,我认为你无法避免多次读取数据库。 - Tim Newton
1
你可能是对的,关于无法在C#中使用Linq使一个数据库读取此实例。我知道这是可以做到的,只是我想要使用Linq的能力,因为它是强类型的,并且与我的干净代码的其余部分相匹配。我已经在较小的表上完美地实现了分页,这些表不包含大数据集或大数据。 - Jason Foglia
63
在调用 Take(50) 之前调用 ToList() 表示你会拉取所有的记录到你的应用程序中。在让服务器执行所有这些工作后,最终只会保留其中的 50 条记录。因此,在调用 ToList() 之前应该先调用 Take(50) - Don Cheadle

10
我最近被这篇Code Project的文章所启发(复制自) Entity Framework 分页 代码:
public async Task<IList<SavedSearch>> FindAllSavedSearches(int page, int limit)
{
    if (page == 0)
        page = 1;

    if (limit == 0)
        limit = int.MaxValue;

    var skip = (page - 1) * limit;

    var savedSearches = _databaseContext.SavedSearches.Skip(skip).Take(limit).Include(x => x.Parameters);
    return await savedSearches.ToArrayAsync();
}

我对Entity Framework没有经验,也没有测试过其性能,因此请谨慎使用 :)


".Include(x => x.Parameters); 的意思是什么?" - toha

2
很容易在SQL Server上实现。
你可以编写以下查询:
select count() over(), table.* from table
count() over()会返回结果中总行数的计数,因此您无需运行两个查询。 请记住,您应该在上下文中运行原始SQL或使用Dapper,它将结果作为视图模型返回。

2
常见的展示数百万条记录的方法是不显示所有页面。想象一下:如果你有数百万条记录,每页显示20甚至100项,那么你将会有数以万计的页面。 展示所有记录没有意义。你可以仅加载当前页面并提供链接到下一页,就这样。 或者你可以加载100-500条记录,但仍然只显示一页,并使用已加载的记录信息生成前几页的页面链接(以确保知道有多少个下一页可用)。

1
你说得对,我也不想展示数百或数千个链接,甚至让用户翻过那么多页。我想我遇到的问题是我只想写一个查询语句。例如:MySQL允许你在一个查询中使用SQL_CALC_FOUND_ROWS,然后再用另一个查询来提取结果。非常高效!让分页变得很棒!感谢@VladimirPerevalov的建议! - Jason Foglia
你还可以提供一个文本框“转到:”和一个按钮,以直接跳转到特定页面。 - user915331
3
通常会显示某种分页控件,以指示总页面/项目数。 - Jonathan Wood
1
@Jonathan Wood,以搜索引擎的结果为例。它们只显示大约10页内容,并提供一个“大致”的总结果数量(但这是另一个问题)。 - Vladimir Perevalov
@Jonathan Wood 我同意,这更取决于实际数据以及它通常的使用方式。我相信有些情况下,用户确实需要快速浏览所有这些数百万(甚至数千)条记录。 - Vladimir Perevalov
显示剩余2条评论

2
如果您需要快速解决方案,可以使用XPagedList https://github.com/dncuug/X.PagedList。XPagedList是一个库,它使您能够轻松地将IEnumerable / IQueryable分割成“页面”,并通过索引获取特定的“页面”。例如:
var products = await _context.Products.ToPagedListAsync(pageNumber, pageSize)

0

我创建了一个NuGet库,可以为您完成分页。https://github.com/wdunn001/EntityFrameworkPaginateCore

将NuGet添加到项目中

安装-Package EntityFrameworkPaginateCore 添加

使用EntityFrameworkPaginateCore; 到您的提供程序

有1个方法和2个重载方法,这些重载方法允许排序和过滤。使用sort对象和filter对象

public async Task<Page<Example>> GetPaginatedExample(
            int pageSize = 10, 
            int currentPage = 1, 
            string searchText = "", 
            int sortBy = 2
            )
        {
            var filters = new Filters<Example>();
                filters.Add(!string.IsNullOrEmpty(searchText), x => x.Title.Contains(searchText));

            var sorts = new Sorts<Example>();
            sorts.Add(sortBy == 1, x => x.ExampleId);
            sorts.Add(sortBy == 2, x => x.Edited);
            sorts.Add(sortBy == 3, x => x.Title);

            try
            {
                return await _Context.EfExample.Select(e => _mapper.Map<Example>(e)).PaginateAsync(currentPage, pageSize, sorts, filters);
            }
            catch (Exception ex)
            {
                throw new KeyNotFoundException(ex.Message);
            }
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接