C# EF查询方法转异步实现报错及性能优化求助
问题根因
你当前代码执行慢、异步方法报错的核心原因有两个:
- 你在过滤条件前加了
AsEnumerable(),这会让EF直接把AP_SUPPLIER_2307整张表的数据全部加载到应用内存中,后续的Where过滤是在内存里遍历全表做的,表数据量越大执行越慢。同时AsEnumerable()返回的是内存集合类型IEnumerable<T>,EF提供的FirstOrDefaultAsync、ToListAsync等异步扩展方法是针对数据库查询类型IQueryable<T>的,自然会报找不到方法的错误。 - 你在
Select映射逻辑里反复调用GetUploadFileDetails(x.UL_ID)、GetTaxCode(),每遍历一条主表记录就会发起一次单独的数据库查询,也就是典型的N+1查询问题,记录越多额外查询次数越多,性能会指数级下降。
注意:异步查询本身不会缩短查询执行的时间,它的作用是在等待数据库返回结果时释放应用线程,避免阻塞请求。你遇到的性能问题本质是查询写法不合理导致的,和同步/异步没有直接关系。
修复方案
按以下步骤调整代码即可解决性能问题,同时正确实现异步查询:
- 移除过滤前的
AsEnumerable()调用,让Where条件被EF翻译成SQL语句,在数据库端完成过滤,只拉取符合headerID条件的目标数据,避免全表加载。 - 提前收集主表记录关联的所有外键ID,批量查询关联的上传文件、税码数据,不要在遍历映射时单条查库,解决N+1问题。
- 用EF提供的
ToListAsync()等异步方法执行实际的数据库IO操作,不要对内存集合调用异步扩展方法。 - 给
AP_SUPPLIER_2307表的AP_2307_HDR_ID字段加数据库索引,能进一步大幅提升过滤速度。
修正后的代码参考:
public async Task<IEnumerable<E2307DetailsViewModel>> GetE2307Details(long headerID) { // 数据库端执行过滤,只拉取目标header对应的数据 var supplierRecords = await entities.AP_SUPPLIER_2307 .Where(x => x.AP_2307_HDR_ID == headerID) .ToListAsync(); if (!supplierRecords.Any()) { return Enumerable.Empty<E2307DetailsViewModel>(); } // 批量查询所有关联数据,避免N+1查询 var relatedUlIds = supplierRecords.Select(x => x.UL_ID).Distinct().ToList(); // 这里需要实现按ID批量查上传文件的方法,替代原来的单条查询 var uploadFileMap = (await GetUploadFileDetailsBatchByIds(relatedUlIds)) .ToDictionary(x => x.UL_ID, x => x); var taxCodeMap = (await GetTaxCodeAsync()) .ToDictionary(x => x.ATCCode, x => x.NatureOfPayment); // 内存中映射视图模型,无额外数据库开销 return supplierRecords.Select(x => { uploadFileMap.TryGetValue(x.UL_ID, out var uploadFile); taxCodeMap.TryGetValue(x.ATCCode, out var natureOfPayment); var taxRate = x.TAX_RATE / 100; return new E2307DetailsViewModel { APSupplier2307ID = x.AP_SUPPLIER_2307_ID, AP2307HeaderID = x.AP_2307_HDR_ID, UploadFileID = x.UL_ID, TransactionAPJEID = x.TRANS_APJE_ID, TransactionDescription = x.TRANS_DESCRIPTION, TransactionDate = x.TRANS_DATE, ReferenceNo = x.REFERENCE_NO, InvoiceNo = x.INVOICE_NO, ATCCode = x.ATC_CODE, TaxRate = x.TAX_RATE, AmtOfTaxWithHeld = x.AMOUNT_OF_TAX_WITHHELD, ForTheMonthOf = uploadFile?.FOR_THE_MONTH_OF, IncomePayment = taxRate == 0 ? 0 : x.AMOUNT_OF_TAX_WITHHELD / taxRate, MonthNo = uploadFile?.FOR_THE_MONTH_OF_NO ?? 0, NatureOfPayment = natureOfPayment, ForTheYearOf = uploadFile?.FOR_THE_YEAR }; }); }
内容的提问来源于stack exchange,提问作者Cedric VH
相关产品推荐
相关产品推荐

