【问题标题】:How to optimize EF performance on foreach statement如何优化 foreach 语句的 EF 性能
【发布时间】:2019-11-07 08:08:01
【问题描述】:

这是我的代码;如您所见,我正在为每一行保存更改,但我希望提高性能,因为我每次都有大量数据,例如 50.000 或 100.000 行或更多。这需要很长时间才能完成。

如何提高我的 EF SaveChanges 性能?我尝试使用一些第三方库进行批量保存、批量更新,但它没有在数据库中更新。更新 50.000 行需要 2 小时。我想为这种方法缩短时间。

private void TransferOrders()
{
    using (var context = new BbsfDbContext())
    {
        context.DisableFilter(AbpDataFilters.MayHaveTenant);
        context.DisableFilter("LanguageSpecificFilter");

        var sapOrders = context.SapOrders
                               .Where(p => p.VBTYP != null && 
                                           p.VBTYP.ToLower() == OrderDocumentType && 
                                           p.IsRead == false)
                                //.Where(p => p.VBTYP != null && p.VBTYP.ToLower() == OrderDocumentType && p.Id == 3025)
                               .Where(p => !ActiveUsersOnly || context.Users.Where(u => u.IsActive).Select(a => a.MainVendor.SapCode).Contains(p.KUNNR))
                               .OrderBy(p => p.CreatedDate)
                               .ToList();

        if (sapOrders.Any())
        {
            foreach (var item in sapOrders)
            {
                try
                {
                    var order = context.Orders.FirstOrDefault(p => p.SapCode == item.VBELN);

                    var isExist = context.SapOrderDetails.Any(p => p.DOCNUM == item.DOCNUM);

                    if (isExist)
                    {
                        var salesOrganization = context.SalesOrganizations.FirstOrDefault(p => p.SapCode == item.VKORG);

                        if (salesOrganization == null)
                            continue;

                        var distributionChannel = context.DistributionChannels.FirstOrDefault(p => p.SapCode == item.VTWEG);

                        if (distributionChannel == null)
                            continue;

                        var salesDepartment = context.SalesDepartments.FirstOrDefault(p => p.SapCode == item.SPART);

                        if (salesDepartment == null)
                            continue;

                        var salesOffice = context.SalesOffices
                                                 .FirstOrDefault(p => p.SapCode == item.VKBUR &&
                                                     p.SalesOrganization.Id == salesOrganization.Id &&
                                                     p.DistributionChannel.Id == distributionChannel.Id &&
                                                     p.SalesDepartment.Id == salesDepartment.Id);
                        if (salesOffice == null)
                            continue;

                        var ordererCustomer = context.Customers
                                .FirstOrDefault(p => p.SapCode == item.KUNNR &&
                                                     p.SalesOrganization.Id == salesOrganization.Id &&
                                                     p.DistributionChannel.Id == distributionChannel.Id &&
                                                     p.SalesDepartment.Id == salesDepartment.Id &&
                                                     p.SalesOffice.Id == salesOffice.Id);

                        var recipientCustomer = context.Customers
                                .FirstOrDefault(p => p.SapCode == item.KUNWE &&
                                                     p.SalesOrganization.Id == salesOrganization.Id &&
                                                     p.DistributionChannel.Id == distributionChannel.Id &&
                                                     p.SalesDepartment.Id == salesDepartment.Id &&
                                                     p.SalesOffice.Id == salesOffice.Id);

                        if (recipientCustomer == null)
                            recipientCustomer = context.Customers
                                    .FirstOrDefault(p => p.SapCode == item.KUNWE &&
                                                         p.SalesOrganization.Id == salesOrganization.Id &&
                                                         p.DistributionChannel.Id == distributionChannel.Id &&
                                                         p.SalesDepartment.Id == salesDepartment.Id &&
                                                         p.SalesOffice == null);

                        if (ordererCustomer == null || recipientCustomer == null)
                            continue;

                        if (order == null)
                        {
                            order = new Order
                                {
                                    SapCode = item.VBELN,
                                    SapOrderDate = item.AUDAT,
                                    DocumentType = context.DocumentTypes.FirstOrDefault(p => p.SapCode == item.VBTYP),
                                    SalesDocument = context.SalesDocuments.FirstOrDefault(p => p.SapCode == item.AUART),
                                    BaseAmount = item.NETWR,
                                    TotalTax = item.MWSBT,
                                    Currency = context.CurrencyDefinitions.FirstOrDefault(p => p.SapCode == item.WAERK),
                                    SalesOrganization = salesOrganization,
                                    DistributionChannel = distributionChannel,
                                    SalesDepartment = salesDepartment,
                                    SalesGroup = context.SalesGroups.FirstOrDefault(p => p.SapCode == item.VKGRP && p.SalesOffice.Id == salesOffice.Id),
                                    SalesOffice = salesOffice,
                                    RequestedDeliveryDate = item.VDATU,
                                    SASNo = item.BSTNK,
                                    SASOrderDate = item.BSTDK ?? item.AUDAT,
                                    OrdererCustomer = ordererCustomer,
                                    RecipientCustomer = recipientCustomer,
                                    //PRSDT
                                    Status = OrderStatus.Approved,
                                    Type = OrderType.MainVendor,
                                    DeliveryAddress = context.CustomerAddressBooks.FirstOrDefault(p => p.MainVendor.Id == ordererCustomer.Id && p.SubVendor.Id == recipientCustomer.Id),
                                    CreationTime = DateTime.Now,
                                    LastModificationTime = DateTime.Now,
                                    CreatorUserId = context.Users.First(p => p.UserName == AbpUserBase.AdminUserName).Id,
                                    LastModifierUserId = context.Users.First(p => p.UserName == AbpUserBase.AdminUserName).Id,
                                    IsSubVendorOrder = false,
                                    IsSameDayDelivery = false,
                                    RepresentativeId = context.Users.First(p => p.UserName == AbpUserBase.AdminUserName).Id
                                    //ProductionSite
                                    //RejectionReason =//todo:bu silinmeli iptal kalem bazında burada statu olmalı
                                };
                                var savedOrder = context.Orders.Add(order);
                                context.SaveChanges();

                                order.SASNo = BbsfConsts.KeasOrderNumberPrefix + savedOrder.Id;
                            }
                            else
                            {
                                order.SapOrderDate = item.AUDAT;
                                order.DocumentType = context.DocumentTypes.FirstOrDefault(p => p.SapCode == item.VBTYP);
                                order.SalesDocument = context.SalesDocuments.FirstOrDefault(p => p.SapCode == item.AUART);
                                order.BaseAmount = item.NETWR;
                                order.TotalTax = item.MWSBT;
                                order.Currency = context.CurrencyDefinitions.FirstOrDefault(p => p.SapCode == item.WAERK);
                                order.SalesOrganization = salesOrganization;
                                order.DistributionChannel = distributionChannel;
                                order.SalesDepartment = salesDepartment;
                                order.SalesGroup = context.SalesGroups.FirstOrDefault(p => p.SapCode == item.VKGRP && p.SalesOffice.Id == salesOffice.Id);
                                order.SalesOffice = salesOffice;
                                order.RequestedDeliveryDate = item.VDATU;
                                order.SASNo = BbsfConsts.KeasOrderNumberPrefix + order.Id;
                                //order.SASOrderDate = item.BSTDK.HasValue ? item.BSTDK : item.AUDAT;
                                order.OrdererCustomer = ordererCustomer;
                                order.RecipientCustomer = recipientCustomer;
                                //PRSDT
                                //order.Status = OrderStatus.Approved;
                                order.DeliveryAddress = context.CustomerAddressBooks.FirstOrDefault(p => p.MainVendor.Id == ordererCustomer.Id && p.SubVendor.Id == recipientCustomer.Id);
                                order.LastModifierUserId = context.Users.First(p => p.UserName == AbpUserBase.AdminUserName).Id;
                                order.LastModificationTime = DateTime.Now;
                                //ProductionSite
                                //RejectionReason =//todo:bu silinmeli iptal kalem bazında burada statu olmalı
                            }
                        }
                        else
                        {
                            if (order != null)
                            {
                                var orderDetails = context.OrderDetails.Where(p => p.OrderId == order.Id).ToList();
                                orderDetails?.ForEach(p => context.OrderDetails.Remove(p));
                                context.SaveChanges();

                                context.Orders.Remove(order);
                                context.SaveChanges();
                            }
                        }

                        item.IsRead = true;
                        item.ModifiedDate = DateTime.Now;

                        context.SaveChanges();
                    }
                    catch (Exception ex)
                    {
                        logger.Error(ex, MethodBase.GetCurrentMethod().Name + " Error During IDOCOperations " + ex.Message);
                        continue;
                    }
                }
            }
        }
    }

【问题讨论】:

  • 如果您有很多内存,您可以将许多 dbset 作为列表预加载到内存中。此外,如果您不更改从 dbcontext 获取的数据,AsNoTracking() 可以为大型数据集提供相当不错的性能提升,因为它不必设置跟踪返回对象的更改。
  • 避免在 foreach 中而不是在外部调用 SavesChanges()
  • 删除if (sapOrders.Any()) - 它什么也没做。 if (ordererCustomer == null || recipientCustomer == null) continue; orderCustomer 检查可以提前移动。
  • 一般来说,任何与 SQL 相关的循环/游标相关问题的答案都是不要!。 SQL 是一种基于集合的语言,它不使用循环。它在单个操作中检索 所有 个相关行,其中一个循环需要 至少 N 个操作。使用循环甚至有一个名称RBAR:Row-By-Agonizing-Row。当您在 client 上执行此操作时,结果会更糟,因为每个查询都需要另一个网络往返
  • 您在初始查询中没有使用联接是否有原因? stackoverflow.com/questions/97197/…

标签: c# asp.net-mvc performance entity-framework


【解决方案1】:

乍一看,似乎可以对不同的操作进行分组,然后在一个步骤中执行。

例如:

  • 创建必须使用item.IsRead = true; item.ModifiedDate = DateTime.Now; 更新的所有项目的列表,并在最后一步执行所有操作
  • 以同样的方式,创建一个包含所有要删除的订单的列表,然后在最后一步执行

我不知道这是否适用于上下文和/或应用程序,这只是一个想法

【讨论】:

    【解决方案2】:

    首先,检查您的延迟加载功能配置,包括全局和副属性级别。

    当您在 sapOrders 中执行 ToList() 时,您正在执行查询(并将结果加载到内存中),并且当您获得 Orders、SalesOrganizations 等时,您可能正在执行边查询...

    查看this 文章以改进您的循环。

    另一方面,您可以在循环中使用异步查询和并行编程,因此,对于其中的每个非依赖任务,您可以创建一个异步任务方法并同时运行所有这些方法。

    Herehere是一些文章希望对你有所帮助。

    祝你好运!

    【讨论】:

      【解决方案3】:

      如果您可以避免在循环中调用.SaveChanges(),而是在最后执行此操作,那么您的情况会好得多,因为您可以避免多次往返数据库。话虽这么说,如果你循环超过 50,000 个项目,你可能希望在某种程度上对它进行批处理,也许每 1,000 个你调用它。

      不幸的是,您需要在保存时创建订单的结果才能存储在另一列中。也许如果您跟踪您正在创建的订单,当您进行批量保存时,在您可以完成之后,您会为刚刚创建的每个订单和另一个 .BulkSaveChanges() 批量设置 SASNo

      对于您以后的保存更改(删除订单详细信息,保存,删除订单,保存),我看不出需要在多个步骤中执行此操作,但可能我的 EF 生锈了,它会抱怨。理想情况下,我会删除所有对 .SaveChanges() 的调用,并在每 1000 次批量操作中执行此操作。

      假设BulkSaveChanges 可以处理所有这些,以上将显着减少数据库网络调用的数量。基本上我的目标是在下面,但归根结底,如果没有 EF,这可能会做得更好/更快。

      using (var context = new BbsfDbContext())
      {
          var sapOrders = ...;
          var ordersCreated = new List<..>(); // might wanna initialized this with a size if you have a rough gauge on what % will need creation of loop
      
          //if (sapOrders.Any()) // not needed
          //{
              foreach (var item in sapOrders.Select((x, index) => new { x, index }))
              {
                  try
                  {
                      var order = ...;
                      var isExist = ...;
      
                      if (isExist)
                      {
                          // ...
      
                          if (order == null)
                          {
                              order = new Order { ... };
                                  var savedOrder = context.Orders.Add(order);
                                  //context.SaveChanges();
      
                                  //order.SASNo = BbsfConsts.KeasOrderNumberPrefix + savedOrder.Id;
                                  ordersCreated.Add(order);
                              }
                              else
                              {
                                  // Do updates
                                  // ...
                              }
                          }
                          else
                          {
                              //if (order != null) // shouldn't need this
                              //{
                                  var orderDetails = context.OrderDetails.Where(p => p.OrderId == order.Id).ToList();
                                  orderDetails?.ForEach(p => context.OrderDetails.Remove(p));
                                  //context.SaveChanges();
      
                                  context.Orders.Remove(order);
                                  //context.SaveChanges();
                              //}
                          }
      
                          // ...
      
                          if (index % 1000 == 0)
                          {
                              context.BulkSaveChanges(); // bulk save of 1000 loops of changes
      
                              foreach (var orderCreated in ordersCreated)
                              {
                                  orderCreated.SASNo = BbsfConsts.KeasOrderNumberPrefix + savedOrder.Id;
                              }
                              context.BulkSaveChanges(); // bulk save of x num of SASNo sets
                          }
                      }
                      catch (Exception ex)
                      {
                          // ...
                      }
                  }
              }
          }
      }
      

      【讨论】:

      • .AsNoTracking() 也可能有所帮助,但我不太确定如果您想预先对数据进行单个查询但只跟踪查询返回的某些部分,您将如何管理它。可能通过一些谷歌搜索来实现。
      猜你喜欢
      • 2017-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-31
      相关资源
      最近更新 更多