在IQueryable上调用的IEnumerable扩展方法会导致性能问题

时间:2019-05-01 20:34:10

标签: c# sql entity-framework extension-methods

每当我在IQueryable上调用接受IEnumerable的扩展方法时,使用该列表/实体的过程的其余部分都会非常缓慢。

POR QUEEEEE?!

该问题似乎与代码的实际结构无关,因为它是最佳选择,当我启动一个新数据库进行单元测试时,该问题似乎没有出现。

这是扩展方法:

        public static Bill FirstById(this IEnumerable<Bill> query, int billId)
        {
            return query.FirstOrDefault(r => r.Id == billId);
        }

使用方法如下:

        public Bill GetDeposit(int depositId)
        {
            var deposit = _depositRepository
                .AndSalesOrder()
                .AndSalesOrderCustomerContact()
                .AndDepositApplications()
                .FirstById(depositId);

            return deposit;
        }

这是实际实体的使用方式:

        public bool ConvertDeposit(List<ConvertDepositSubmitModel> conversions)
        {
            if (conversions.Any())
            {
                var depositId = conversions.Select(c => c.DepositId)
                .Distinct()
                .Single();

                var billPaymentIds = conversions.Select(c => c.BillPaymentId);

                var deposit = _dataProvider.GetDeposit(depositId);

                var billPayments = _dataProvider.GetBillPayments(billPaymentIds);

                var totalConversionAmount = conversions.Sum(c => c.Amount);

                var unappliedDepositAmount = (deposit.BillStatusId == BillStatus.Credited ? 0 : deposit.TotalSell - deposit.Balance) - deposit.DepositApplications.Select(a => a.Amount).DefaultIfEmpty(0).Sum();

                if (unappliedDepositAmount != totalConversionAmount)
                {
                    throw new Exception("The provided conversion amount would not fully convert the Deposit.");
                }

                _unitOfWork.TryTransactionAndCommit(() =>
                {

                    foreach (var conversion in conversions)
                    {
                        var billPayment = billPayments.FirstByBillPaymentId(conversion.BillPaymentId);


                        this.CreateBillPaymentBill(deposit, conversion);

                        this.CreateMoneyOnAccountTransaction(deposit, conversion);

                        this.UpdateBillPayment(conversion, billPayment);
                    }

                    this.UpdateNetProceeds(billPayments);

                    this.ApplyCreditCardFees(billPaymentIds);

                    var customerCredit = this.CreateCustomerCredit(deposit, totalConversionAmount);

                    this.CreateCustomerCreditBill(deposit, customerCredit);

                    this.UpdateDeposit(deposit, totalConversionAmount);
                });
            }
            else
            {
                throw new Exception("The provided set of bill payments was empty.");
            }

            return true;
        }

我们看到,每种经过严格测试的方法都可以产生以下诊断结果:

PV2ANZAC                
GetDeposit: 33434ms
GetBillPayments: 54ms
CreateBillPaymentBill1: 17775ms
CreateMoneyOnAccountTransaction1: 10774ms
UpdateBillPayment1: 10810ms
UpdateNetProceeds: 18130ms
ApplyCreditCardFees: 17206ms
Insert CustomerCredit: 10795ms
CustomerCredit SaveChanges: 16276ms
CreateCustomerCredit: 27075ms
CreateCustomerCreditBill: 10688ms

我们绝对希望所有事物都比它至少少一个数量级。

2 个答案:

答案 0 :(得分:0)

为什么要创建一个接受IEnumerable的扩展方法?

很显然,当您在IQueryable上调用IEnumerable扩展方法时,IQueryable将会被水化,当您只需要一个时,便会在Bill表中引入每一行!

现在,如果我们可以假定GetBillPayments正在类似地调用数据库,则可以在这里解释差异:

GetDeposit: 33434ms
GetBillPayments: 54ms

答案 1 :(得分:0)

检查以下两件事:

1)您正在使用更改跟踪,这可能会导致速度缓慢。参见:https://weblog.west-wind.com/posts/2014/dec/21/gotcha-entity-framework-gets-slow-in-long-iteration-loops#Turn-off-Change-Tracking

2)您要从数据库中提取一堆记录,然后在内存中查询该记录集,而不是在数据库级别查询您想要的记录。检查EF发送的查询,并确保没有在存储库级别实现该列表。