Массовая вставка Entity Framework нереально медленная

Я использую EF 6. Я пытаюсь вставить около 200 000 объектов, сохраняя изменения в базе данных после каждых 100 объектов.

Проблема в том, что на сохранение 50 000 сущностей ушло 11 часов, и он все еще отстает. Я запускаю это с помощью WebJobs, и задание публикуется в том же лазурном веб-приложении, что и основной веб-сайт. Является ли проблема из-за того, что WebJob не хватает ресурсов, сохранения после 100 объектов или подхода?

Метод

public void SaveLeadsForBuyer(ISenderModel model)
{
    var rowCounter = 0;

    foreach (var deliveryRecord in model.Customers.Select(customerModel => new DeliveryRecord()
    {
        BuyerId = model.Buyer.Id,
        AspNetUserId = customerModel.Id,
        DeliveryType = model.Buyer.DeliveryType,
        CreatedOn = DateTime.UtcNow
    }))
    {
        ++rowCounter;

        _unit.Repository<DeliveryRecord>().Insert(deliveryRecord);

        _unit.SaveChangesPartially(rowCounter, 100);
    }

    _unit.SaveChanges();
}

Помощник

public static class UnitOfWorkHelper
{
    /// <summary>
    /// Helper method triggers SaveChanges() after amount of rows provided through "amount" parameter in method
    /// </summary>
    /// <param name="unit">UnitOfWork object</param>
    /// <param name="count">Current amount of rows</param>
    /// <param name="saveCount">Amount when to save changes to database</param>
    public static void SaveChangesPartially(this IUnitOfWorkAsync unit, int count, int saveCount)
    {
        if (count % saveCount == 0)
        {
            unit.SaveChanges();
        }
    }
}

person sensei    schedule 27.03.2016    source источник
comment
EF очень плохо подходит для массовой вставки, INSERT SELECT будет работать в 1000000 раз быстрее, чем EF.   -  person Akash Kava    schedule 27.03.2016


Ответы (1)


Это медленно, потому что Entity Framework выполняет обход базы данных для каждой записи. Таким образом, если вы сохраните 200 000 сущностей, будет выполнено 200 000 обращений к базе данных, что далеко не оптимально для сохранения нескольких сущностей.

Для такого сценария вам необходимо реализовать себя или использовать библиотеку, поддерживающую BulkInsert (которая обычно выполняет SqlBulkCopy под капотом).

Существует 3 основные библиотеки (2 FREE, 1 PRO), которые позволяют выполнять массовую вставку.

// Example from Entity Framework Extensions Library
using (var ctx = new EntitiesContext())
{
    ctx.BulkInsert(list);
}

Вы можете прочитать следующую статью, чтобы понять ЗА и ПРОТИВ каждой библиотеки: Entity Framework — обзоры и сравнения библиотек массовой вставки

Расширения Entity Framework — это библиотека, которая предлагает наибольшую гибкость (массовая вставка, обновление, удаление, объединение и BulkSave Изменяет и поддерживает все) однако это PRO версия. Если вы ищете бесплатную версию, я рекомендую использовать EntityFramework.BulkInsert, однако она больше не поддерживается и не поддерживает все ассоциации и наследование.

Отказ от ответственности: я являюсь владельцем проекта Entity Framework Extensions

EDIT: ответьте на вопрос комментария

Я сохраняю каждые 100 записей, а не каждую запись

Неважно, добавляете ли вы одну сущность или 100 сущностей в контекст своего модуля, Entity Framework сохраняет их одну за другой (один оператор вставки для каждой записи). Просто используйте SQL Profiler с базой данных SQL Server, и вы поймете, что я имею в виду.

EDIT: ответьте на вопрос комментария

великий Джонатан. есть ли способ реализовать это с помощью ef6 generic uow?

Ответ зависит от того, какую библиотеку вы решите использовать.

Если вы используете мою библиотеку, вы можете создать метод BulkSaveChanges или изменить в своем UnitOfWork все "_context.SaveChanges()" на "_context.BulkSaveChanges()"

public void SaveLeadsForBuyer(ISenderModel model)
{
    // ... code ...
    // _unit.SaveChanges();
    _unit.BulkSaveChanges();
}

Если вам нужна лучшая производительность и реализация массовой вставки из моей библиотеки или БЕСПЛАТНОЙ библиотеки, я бы, вероятно, добавил метод или метод расширения (если вы не можете изменить класс репозитория) с именем BulkInsert.

public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
    // ... code ...

    public virtual void BulkInsert(List<TEntity> list)
    {
        _context.BulkInsert(list);
    }
}

Имейте в виду, что BulkInsert напрямую вставляет сущности без вызова «SaveChanges», он не использует средство отслеживания контекста/изменений для достижения оптимальной производительности.

person Jonathan Magnan    schedule 27.03.2016
comment
Я сохраняю каждые 100 записей, а не каждую запись. - person sensei; 27.03.2016
comment
Не имеет значения. Вы используете инструмент, неподходящий для работы. EF не предназначен для массовой передачи. Как говорит Джонатан, SqlBulkCopy написан кем-то, кого следует за это уволить (крайне плохое поведение блокировки)... Я написал свой собственный класс за полдня, который создает временную таблицу, массовые копии sql в нее, а затем использует одна команда sql для копирования данных в итоговую таблицу. Избегает нелепой логики блокировки в SqlBulkCopy (которая пытается получить эксклюзивную блокировку на 30 секунд в цикле без ожидания, поэтому, если в таблице есть какая-либо активность, она никогда не получает ее). - person TomTom; 27.03.2016
comment
великий Джонатан. есть ли способ реализовать это с помощью ef6 generic uow? - person sensei; 27.03.2016