EF6带字段筛选的Count查询过慢(大数据量场景)
解决EF6中大数据量成员统计的性能问题
这个问题的核心在于延迟加载导致的N+1查询/内存级数据处理,你之前的两种做法都会让EF把大量数据拉到内存再计算,或者反复发起数据库请求,这在40万级数据量下必然卡顿。下面给你几个高效的解决方案,完全贴合你想要的SQL子查询效果:
1. 在查询阶段直接投影统计(最优方案)
既然你是在获取企业列表时需要统计数,那最直接的方式就是让数据库一次性完成所有统计,而不是等映射阶段再去触发查询。你可以在Controller查询Company的时候,用EF的Select投影直接计算每个公司的有效成员数,EF会自动把这个转化为你想要的子查询SQL:
// 在CompanyController中获取企业列表的逻辑 var companyData = db.Companies .Select(c => new { // 带上Company实体或所需属性 Company = c, // 让数据库执行统计,而非内存计算 EmployeeCount = c.MembershipUser.Where(mu => !mu.Deleted).Count() }) .ToList(); // 映射到ViewModel,此时已包含统计数,无需访问数据库 var viewModelList = companyData.Select(item => { var vm = CrmViewModelMapping.CompanyListToCompanyViewModel(item.Company); vm.EmployeeCount = item.EmployeeCount; return vm; }).ToList();
这个查询会被EF翻译成类似你想要的SQL:
SELECT c.*, (SELECT COUNT(*) FROM MembershipUser mu WHERE mu.CompanyID = c.CompanyID AND mu.Deleted = 0) as EmployeeCount FROM Company c
所有统计都在数据库端完成,性能和你直接执行SQL完全一致。
2. 用中间DTO传递统计数据(更规范的做法)
如果你的映射方法不想直接处理匿名类型,可以定义一个简单的DTO类,专门用来传递带统计的公司数据:
// 定义中间DTO public class CompanyWithCountDto { public int Id { get; set; } public string Name { get; set; } // 其他Company需要的属性 public int EmployeeCount { get; set; } } // 查询时直接投影到DTO var companyDtos = db.Companies .Select(c => new CompanyWithCountDto { Id = c.Id, Name = c.Name, EmployeeCount = c.MembershipUser.Where(mu => !mu.Deleted).Count() }) .ToList(); // 修改映射方法,让它接收DTO而非Company实体 var viewModelList = companyDtos.Select(dto => CrmViewModelMapping.CompanyListToCompanyViewModel(dto)).ToList();
这种方式更清晰,也避免了传递整个Company实体可能带来的冗余数据。
为什么你之前的方法慢?
- 第一种代码
company.MembershipUser.Count(x => x.Deleted == false):因为MembershipUser是虚拟属性,EF会触发延迟加载——每处理一个Company,就会单独发一次SQL查询该公司的MembershipUser数量,N个Company就会发N次请求,40万数据量下这就是灾难。 - 第二种计算属性:同样触发延迟加载,会把该公司的所有MembershipUser数据都加载到内存,再在内存里过滤计数,40万条数据加载到内存本身就耗时极长,更别说后续计算了。
额外优化建议
- 给
MembershipUser表的CompanyID和Deleted字段加联合索引:CREATE INDEX IX_MembershipUser_CompanyID_Deleted ON MembershipUser(CompanyID, Deleted);,这会让数据库的Count查询速度再上一个台阶。 - 关闭不必要的延迟加载:在你的DbContext构造函数里加上
this.Configuration.LazyLoadingEnabled = false;,避免不小心触发N+1查询。
内容的提问来源于stack exchange,提问作者ransems
相关产品推荐
相关产品推荐

