EF Core - 为啥显式加载非常慢?
Posted
技术标签:
【中文标题】EF Core - 为啥显式加载非常慢?【英文标题】:EF Core - why Explicit Loading is terribly slow?EF Core - 为什么显式加载非常慢? 【发布时间】:2019-01-24 10:27:29 【问题描述】:我正在使用域驱动设计在 .Net Core 中开发微服务。基础设施层有 EF Core DbContext 来访问数据库,在我的存储库中,我有异步方法来检索数据。
因为 Include/ThenInclude 不支持过滤(至少不支持 Ef Core 2.1),所以我在谷歌搜索如何替换 Include 时尝试了所有可能的方法。我也观看了有关 Ef Core 的 Pluralsight 视频,当我看到显式加载选项时,我真的很高兴,因为它能够过滤相关对象,但是当我将其中一个方法重写为显式版本时,查询运行了几个毫秒上升到几分钟!
在我的实体配置中,我设置了所有导航和外键,但我不确定显式加载是否需要任何额外设置?在推荐使用全局过滤器之前,请注意 Where 子句通常较长,因此下面的示例只是实际过滤器的简化版本!
这就是我的方法的样子(TransferService 用作聚合,TransferServiceDetail 和任何其他类都只是 TransferService 域中的实体):
public async Task<IEnumerable<TransferService>> GetAllAsync(
TransferServiceFilter transferServiceFilter)
int? pageIndex = null;
int? itemsPerPage = null;
IEnumerable<TransferService> filteredList = DBContext.TransferServices.Where(
ts => !ts.IsDeleted); //This one itself is quick.
//This is just our filtering, it does not affect performance.
if (transferServiceFilter != null)
pageIndex = transferServiceFilter.PageIndex;
itemsPerPage = transferServiceFilter.ItemsPerPage;
filteredList = filteredList.Where(f =>
(transferServiceFilter.TransferSupplierId == null ||
f.TransferSupplierId == transferServiceFilter.TransferSupplierId) &&
(transferServiceFilter.TransferDestinationId == null ||
f.TransferDestinationId == transferServiceFilter.TransferDestinationId) &&
(transferServiceFilter.TransferSupplierId == null ||
f.TransferSupplierId == transferServiceFilter.TransferSupplierId) &&
(string.IsNullOrEmpty(transferServiceFilter.TransportHubRef) ||
f.NormalizeReference(f.TransportHubRef) ==
f.NormalizeReference(transferServiceFilter.TransportHubRef)));
//This is just for paging and again, this is quick.
return await FilterList(filteredList.AsQueryable(), pageIndex, itemsPerPage);
public async Task<IEnumerable<TransferService>> GetAllWithServiceDetailsAsync(
TransferServiceFilter transferServiceFilter)
IEnumerable<TransferService> returnList = await GetAllAsync(
transferServiceFilter);
//This might be the problem as I need to iterate through my TransferServices
//to be able to load all TransferServiceDetails that belong to each individual
//Service.
foreach (TransferService service in returnList)
await DBContext.Entry<TransferService>(service)
.Collection(ts => ts.TransferServiceDetails.Where(
tsd => !tsd.IsDeleted)).LoadAsync();
return returnList;
在我的存储库中,我还有其他方法,类似地引用以前的 GetAllXY... 方法(TransferServiceDetails 有 Rates,Rates 有 Periods 等...)。
我的想法是在我只需要 TransferService 数据时简单地调用 GetAllAsync(并且仅此方法就闪电般快速),或者当我还需要所选服务的详细信息等时调用 GetAllWithServiceDetailsAsync,但我在这个父级中的位置越低-child 层次结构,执行变得越慢,我说的是几分钟,而不仅仅是额外的几毫秒,或者在最坏的情况下是几秒钟。
所以我的问题又来了:我可能从显式加载所需的实体配置中遗漏了任何其他设置,或者只是我的查询不正确?或者,只有当只有一个 TransferService 作为父项而不是 TransferServices 列表(在我的情况下为 50-100)并且只有几个与子项相关的实体(在我的情况下,我通常有 5- 10 个详细信息,每个详细信息有 2-3 个费率,每个费率正好有 1 个期间,等等...)?
【问题讨论】:
看Model-level query filters。 谢谢,但正如我在操作中提到的,我不能使用这些过滤器。有时我需要 IsDeleted 字段,有时我不需要。有时我应用了 x、y、z 过滤器,有时只应用了其中的一部分。 【参考方案1】:我猜您的过滤无法转换为 SQL Where 并且所有过滤都发生在客户端(EF 将所有 TransferServices
实体加载到内存中,在内存中过滤并丢弃不匹配的内容)。
我可以通过启用详细(调试)日志记录来检查这一点 - EF 会将 SQL 转储到日志中。
在您确认后,您应该进行改进:
首先,将if
s 放在 Where 中。而不是:
filteredList = filteredList.Where(f => transferServiceFilter.TransferSupplierId == null ||
f.TransferSupplierId == transferServiceFilter.TransferSupplierId)
使用
if (transferServiceFilter.TransferSupplierId != null)
filteredList = filteredList.Where(f => f.TransferSupplierId == transferServiceFilter.TransferSupplierId)
其次,你应该重新考虑NormalizeReference
。这不能在服务器端执行,因为 SQL 服务器不知道这个实现。您应该预先规范化TransportHubRef
,将其保存在数据库中(例如,NormalizedTransportHubRef
)并使用简单相等的Where
。
(另外,不要忘记索引)。
【讨论】:
谢谢,但正如我提到的,原来的 TransferService 查询速度很快。目前,当我没有指定过滤器时,返回了 220 个服务。当我显式加载详细信息集合时会发生性能损失。即使加载第一个服务的详细信息也需要将近一秒钟,它会加载第一个服务当前存在的 4 条详细记录。 启用日志记录并检查 SQL。您有客户端过滤,当将“全选”与 JOIN 结合使用时,不能保证任何“闪电般的速度”。我想你会对 SQL 文本感到非常惊讶...以上是关于EF Core - 为啥显式加载非常慢?的主要内容,如果未能解决你的问题,请参考以下文章
Entity Framework Core 数据查询原理详解