EF Core是一个及其超卓的 ORM(目标联系映射)工具,十分适合构建 .NET 应用程序。但它和其他工具一样,假如运用不当,或许影响功用。

在这里向你展现一个简略的办法,能够提高接近4倍的功用。这也不是说你一定会得到相同的功用提高,但了解其核心肯定会对查询速度有改善。

存在功用问题的查询

本文用以下示例来解释这个概念。 它取自一个真实的出产应用程序,但进行了简化。

运用一个InvoiceService来获取给定公司的发票调集。这些发票或许来自第三方API或某些其他持久化存储。现在还缺乏详细的发票行信息,所以需求查询数据库来添补缺失的行信息。

下面的LINQ查询自身并没有太大问题。它经过一次数据库查询回来某个发票的一切行。

但由于咱们要对发票调集进行轮询,会多次查询贮存行的数据表。所以这段代码有更多的功用提高空间。

app.MapGet("invoices/{companyId}", (
    long companyId,
    InvoiceService invoiceService,
    AppDbContext dbContext) =>
{
    IEnumerable<Invoice> invoices = invoiceService.GetForCompanyId(
        companyId,
        take: 10);
    var invoiceDtos = new List<InvoiceDto>();
    foreach (var invoice in invoices)
    {
        var invoiceDto = new InvoiceDto
        {
            Id = invoice.Id,
            CompanyId = invoice.CompanyId,
            IssuedDate = invoice.IssuedDate,
            DueDate = invoice.DueDate,
            Number = invoice.Number
        };
        var lineItemDtos = await dbContext
            .LineItems
            .Where(li => invoice.LineItemIds.Contains(li.Id))
            .Select(li => new LineItemDto
            {
                Id = li.Id,
                Name = li.Name,
                Price = li.Price,
                Quantity = li.Quantity
            })
            .ToArrayAsync();
        invoiceDto.LineItems = lineItemDtos;
        invoiceDtos.Add(invoiceDto);
    }
    return invoiceDtos;
})

处理这个问题的计划也很直接。便是提早查询一切行项目,而不是为每张发票获取行信息。

运用批处理

查询办法是相同的,但重构为只查询一次行项目。这意味着只要一次数据库拜访。

终究设计包括三个组成部分:

  1. 将LineItemIds映射到一个HashSet中,这样能够去除重复项。
  2. 经过单次数据库往复查询一切LineItems。
  3. 创建一个LineItemDto字典以便快速查找。

一旦咱们有了字典,咱们就能够遍历发票并分配行项目。填充行项目变成了字典查找(成本低)而不是数据库查询(成本高)。

在运用这个处理计划之前,还要考虑一件工作, 即一次能从数据库加载多少记载?

假定每张发票均匀包括约20个行项目,咱们只获取十张发票。在最坏的情况下(一切行项目都是仅有的),咱们从数据库加载约200个行项目。大多数数据库能够处理这种负载。但假如你正在读取成千上万行,情况又不一样了, 需求考虑其他计划,例如分页查询。

app.MapGet("invoices/{companyId}", (
    long companyId,
    InvoiceService invoiceService,
    AppDbContext dbContext) =>
{
    IEnumerable<Invoice> invoices = invoiceService.GetForCompanyId(
        companyId,
        take: 10);
    HashSet<long> lineItemIds = invoices
        .SelectMany(invoice => invoice.LineItemIds)
        .ToHashSet();
    var lineItemDtos = await context
        .LineItems
        .Where(li => lineItemIds.Contains(li.Id))
        .Select(li => new LineItemDto
        {
            Id = li.Id,
            Name = li.Name,
            Price = li.Price,
            Quantity = li.Quantity
        })
        .ToListAsync();
    Dictionary<long, LineItemDto> lineItemsDictionary =
        lineItemDtos.ToDictionary(keySelector: li => li.Id);
    var invoiceDtos = new List<InvoiceDto>();
    foreach (var invoice in invoices)
    {
        var invoiceDto = new InvoiceDto
        {
            Id = invoice.Id,
            CompanyId = invoice.CompanyId,
            IssuedDate = invoice.IssuedDate,
            DueDate = invoice.DueDate,
            Number = invoice.Number,
            LineItems = invoice
                .LineItemIds
                .Select(li => lineItemsDictionary[li])
                .ToArray()
        };
        invoiceDtos.Add(invoiceDto);
    }
    return invoiceDtos;
})

会快多少?

批处理版别看起来会更快, 真是这样吗?

在第一个版别中,有N个查询(每张发票一个),而在批处理版别中,只要一个查询。

以下是运用BenchmarkDotNet得到的基准测验成果:

用批处理使EF Core查询速度提高 3倍

foreach版别均匀耗时1913.3微秒(us), 批处理版别均匀耗时558.6微秒。

批处理版别快了3.42倍。这是运用本地SQL数据库的成果。

假如查询远程数据库,批处理版别应该会更快,由于网络往复时刻的影响。当有N个查询(foreach版别)时,这种时刻会快速累积。

总结

这个办法的好处在于其简略性和效率。经过批处理数据库查询,明显减少了对数据库的拜访次数。这通常是最大的功用瓶颈之一。

但也要了解,这种办法并不是全能的。

EF Core提供了许多功用和优化办法,但怎么有效运用它们取决于开发者。

最后,永远记得进行基线测验。在这个案例中看到的改善是经过基线测验量化的。没有恰当的测量,很容易做出那些无意中下降功用的, 适得其反的修改。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。