Как оптимизировать производительность EF в выражении foreach - PullRequest
0 голосов
/ 07 ноября 2019

Вот мой код;Как видите, я сохраняю изменения для каждой строки, но хочу повысить производительность, потому что каждый раз у меня большой объем данных, например, 50 000 или 100 000 строк или более. На это уходит много времени.

Как мне улучшить производительность EF SaveChanges? Я пробовал Bulksave, Bulstddate с какой-либо сторонней библиотекой, но она не обновляется в базе данных. Обновление 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;
                    }
                }
            }
        }
    }

Ответы [ 3 ]

1 голос
/ 07 ноября 2019

На первый взгляд кажется, что различные действия можно сгруппировать, а затем выполнить за один шаг.

Например:

  • Создать список всех элементов, которые должныобновиться до item.IsRead = true; item.ModifiedDate = DateTime.Now; и выполнить все вместе в конце за один шаг
  • Таким же образом, создать список со всеми удаляемыми ордерами, а затем выполнить за один шаг в конце

Я не знаю, будет ли это применимо в зависимости от контекста и / или приложения, это просто идея

0 голосов
/ 07 ноября 2019

Если вы можете избежать вызова .SaveChanges() в цикле, а лучше сделать это в самом конце, вам будет намного лучше, так как вы избежите многократных обращений к БД. Тем не менее, если вы делаете из этого более 50000 элементов, возможно, вы захотите сделать это в некоторой степени, возможно, из 1000, которые вы называете.

К сожалению, у вас возникли сложности, связанные с необходимостью создания заказа при сохранении для сохранения в другом столбце. Возможно, если вы отслеживаете создаваемые вами заказы, когда вы сохраняете пакет, сразу после того, как вы можете сделать это, вы делаете пакетный набор SASNo для каждого только что созданного и другого .BulkSaveChanges()?

Для ваших последующих изменений сохранения (когда вы удаляете детали заказа, сохраняете, удаляете заказ, сохраняете), я не вижу необходимости делать это в несколько шагов, но, возможно, мой 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)
                {
                    // ...
                }
            }
        }
    }
}
0 голосов
/ 07 ноября 2019

Прежде всего, проверьте свою конфигурацию для функции отложенной загрузки, как глобального, так и уровня побочных свойств.

Когда вы выполняете ToList () в sapOrders, вы выполняете свой запрос (и загружаете результат впамяти), и вы можете выполнять побочные запросы при получении заказов, SalesOrganizations и т. д. ...

Взгляните на эту статью, чтобы улучшить ваш цикл.

С другой стороны, вы можете использовать асинхронные запросы и параллельное программирование в цикле, поэтому для каждой независимой задачи в нем вы можете создать метод асинхронной задачи и запускать их все одновременно.

Здесь и здесь Вы несколько статей, я надеюсь помочь вам.

Удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...