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 转储到日志中。

在您确认后,您应该进行改进:

首先,将ifs 放在 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 - 为啥显式加载非常慢?的主要内容,如果未能解决你的问题,请参考以下文章

EF6 中的 Eager 、 Lazy 和显式加载

Entity Framework Core 数据查询原理详解

Entity Framework Core 数据查询原理详解

处理EF第一次加载过慢问题

EF Core Eager 加载返回 null

渴望加载多对多 - EF Core [关闭]