You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EF Core 3.1中Linq查询是否高效最优?可优化为链式写法吗?

EF Core 3.1 查询优化及写法调整方案

一、现有查询的性能问题

你当前写的查询最大的性能瓶颈是两处过早调用的.AsEnumerable():

  • 这会直接把_journalLineRepository和_partyRepository的全表数据加载到内存,再在内存中执行分组、关联、聚合操作,数据量稍大就会出现内存占用过高、查询耗时超长的问题,完全没有用到数据库的计算能力,性能非常差。
  • 另外vg.First().Voucher的写法隐含两个问题:一是分组后取第一条的规则不明确,同一个AccountId下如果有多个不同的Voucher,返回结果不可控;二是提前加载全量Voucher数据到内存,产生大量不必要的IO开销。

二、vg.First().Voucher的优化方案

首先需要明确业务规则:同一个AccountId下取哪个Voucher?如果是取最新生成的、或者ID最大的,建议在数据库层面就明确规则,不要在内存隐式取第一条:

  • 如果业务上同一个AccountId对应的所有JournalLine的Voucher完全相同,可以直接把VoucherId、VoucherDate、ProjectId等需要的字段加入分组键,不需要单独取First()。
  • 如果确实需要取分组内的某一条,可在分组前按规则排序(比如按VoucherDate倒序),再在分组后取第一条的对应字段,EF Core 3.1可支持这类逻辑转SQL。

三、最佳开发实践

  • 除非明确需要内存处理,否则不要提前调用AsEnumerable()、ToList()等触发数据加载的方法,尽可能让所有分组、关联、聚合逻辑在数据库端执行,性能会提升数倍甚至数十倍。
  • 关闭EF Core的客户端评估配置,避免写法不支持转SQL时,框架偷偷把全表数据拉到内存计算,出问题很难排查。
  • 查询ViewModel时优先用选择性投影,不需要写Include加载全量关联实体,EF Core会自动根据你投影的字段生成关联查询,只返回需要的字段,减少IO开销。
  • 分组取单条数据时必须明确排序规则,禁止无排序直接调用First(),避免不同环境、不同数据量下返回结果不一致。
  • 无法转SQL的C#端方法(比如你代码里的ToDisplay()枚举扩展方法),放在最后一步内存投影时再调用,不要放在数据库查询的逻辑里。

四、优化后的链式写法示例

// 先在数据库层面执行所有查询逻辑
var dbQuery = _journalLineRepository.TableNoTracking
    .GroupBy(v => v.AccountId)
    .Select(vg => new 
    {
        AccountId = vg.Key,
        Credit = vg.Sum(v => v.Credit),
        Debit = vg.Sum(v => v.Debit),
        // 示例按凭证日期倒序取最新的一条凭证,可根据业务规则调整排序逻辑
        Voucher = vg.OrderByDescending(v => v.Voucher.VoucherDate).Select(v => v.Voucher).FirstOrDefault()
    })
    // 关联往来单位表
    .Join(
        _partyRepository.TableNoTracking,
        vg => vg.AccountId,
        p => p.AccountId,
        (vg, p) => new 
        {
            vg.AccountId,
            vg.Credit,
            vg.Debit,
            vg.Voucher.ProjectId,
            vg.Voucher.VoucherDate,
            p.Id,
            p.FullName,
            // 直接投影激活状态的手机号
            PhoneNo = p.PartyPhones.Where(pp => pp.IsActive).Select(pp => pp.Phone).FirstOrDefault()
        }
    );

// 加载到内存后处理枚举转换逻辑,生成最终ViewModel
var result = dbQuery.AsEnumerable()
    .Select(item => new PartyDeptorAndCreditorViewModel
    {
        PartyId = item.Id,
        FullName = item.FullName,
        PhoneNo = item.PhoneNo,
        ProjectId = item.ProjectId,
        AccountId = item.AccountId.Value,
        Creditor = item.Credit,
        Deptor = item.Debit,
        Balance = item.Credit - item.Debit,
        VoucherDate = item.VoucherDate,
        VoucherRegisterDate = item.VoucherDate,
        BalanceType = item.Debit > item.Credit 
            ? AccountingComplexEnum.ShowPartyBalanceParamSearch.Deptor.ToDisplay(DisplayProperty.Name) 
            : item.Debit < item.Credit 
                ? AccountingComplexEnum.ShowPartyBalanceParamSearch.Creditor.ToDisplay(DisplayProperty.Name) 
                : AccountingComplexEnum.ShowPartyBalanceParamSearch.ZeroBalance.ToDisplay(DisplayProperty.Name)
    }).ToList();

如果EF Core 3.1对上述GroupBy逻辑转SQL失败,可以调整为先查分组聚合结果,再单独关联Voucher表,依然比全表加载到内存性能好很多。

内容的提问来源于stack exchange,提问作者Shahram

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.21 08:36:03