Entity Framework, массовые вставки и поддержание отношений

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

Коллекция точек карты потенциально может быть довольно большой для данной MapLine, и для MapLayer может быть довольно большое количество MapLines.

Вопрос здесь заключается в том, каков наилучший подход для вставки объекта MapLayer в базу данных с использованием Entity Framework и сохранения отношений, которые определяются свойствами навигации?

Стандартная реализация Entity Framework

dbContext.MapLayers.Add(mapLayer);
dbContext.SaveChanges();

вызывает большой всплеск памяти и довольно плохое время возврата.

Я попытался реализовать пакет EntityFramework.BulkInsert, но он не соблюдает отношения объектов.

Кажется, что это будет проблемой, с которой кто-то сталкивался раньше, но я не могу найти какие-либо ресурсы, объясняющие, как выполнить эту задачу.

Обновить

Я пытался реализовать предложение Ричарда, но я не понимаю, как мне поступить с вложенной сущностью, такой как описанная мной. Я работаю в предположении, что мне нужно вставить объект MapLayer, затем MapLines, затем MapPoints, чтобы соблюдать отношения PF/FK в базе данных. В настоящее время я пытаюсь следующий код, но это не кажется правильным.

dbContext.MapLayers.Add(mapLayer);
dbContext.SaveChanges();

List<MapLine> mapLines = new List<MapLine>();
List<MapPoint> mapPoints = new List<MapPoint>();
foreach (MapLine mapLine in mapLayer.MapLines)
{
    //Update the mapPoints.MapLine properties to reflect the current line object
    var updatedLines = mapLine.MapPoints.Select(x => { x.MapLine = mapLine; return x; }).ToList();

    mapLines.AddRange(updatedLines);
}

using (TransactionScope scope = new TransactionScope())
{
    MyDbContext context = null;
    try
    {
        context = new MyDbContext();
        context.Configuration.AutoDetectChangesEnabled = false;

        int count = 0;
        foreach (var entityToInsert in mapLines)
        {
            ++count;
            context = AddToContext(context, entityToInsert, count, 100, true);
        }

        context.SaveChanges();
    }
    finally
    {
        if (context != null)
            context.Dispose();
    }

    scope.Complete();
}

Обновление 2

Попробовав несколько разных способов для достижения этой цели, я, наконец, сдался и просто вставил MapLayer как сущность и сохранил отношение MapLines => MapPoints как необработанную строку Json в байтовом массиве на сущности MapLayer (так как я не запрашиваю эти структуры это работает для меня).

Как говорится, "это не красиво, но работает".

У меня был некоторый успех с пакетом BulkInsert и управлением отношениями вне EF, но я снова столкнулся с проблемой памяти при попытке использовать EF для возврата данных в систему. Кажется, что в настоящее время EF не способен эффективно обрабатывать большие наборы данных и сложные отношения.

2 ответа

Решение

У меня был плохой опыт с огромным сохранением контекста. Все эти рекомендации по сохранению в итерациях по 100 строк, по 1000 строк, затем избавиться от контекста или очистить список и отсоединить объекты, присвоить нулю все и т. Д. И т. Д. У нас были требования ежедневно вставлять миллионы строк во многие таблицы. Определенно не следует использовать сущность в этих условиях. Вы будете бороться с утечками памяти и уменьшением скорости вставки, когда итерации продолжатся.

Нашим первым улучшением было создание хранимых процедур и добавление их в модель. Это в 100 раз быстрее, чем Context.SaveChanges()и нет утечек, нет снижения скорости с течением времени.

Но этого было недостаточно для нас, и мы решили использовать SqlBulkCopy, Это супер быстро. В 1000 раз быстрее, чем при использовании хранимых процедур.

Поэтому мое предложение будет таким: если у вас есть много строк для вставки, но их количество меньше 50000 строк, используйте хранимые процедуры, импортированные в модель; если у вас есть сотни тысяч строк, иди и попробуй SqlBulkCopy,

Вот некоторый код:

EntityConnection ec = (EntityConnection)Context.Connection;
SqlConnection sc = (SqlConnection)ec.StoreConnection;

var copy = new SqlBulkCopy(sc, SqlBulkCopyOptions.CheckConstraints | SqlBulkCopyOptions.Default , null);

copy.DestinationTableName = "TableName";
copy.ColumnMappings.Add("SourceColumn", "DBColumn");
copy.WriteToServer(dataTable);
copy.Close();

Если вы используете DbTransaction с помощью контекста вы также можете выполнить массовую вставку, используя эту транзакцию, но для этого нужны некоторые хаки.

Массовая вставка - не единственный способ эффективного добавления данных с использованием Entity Framework - в этом ответе подробно описан ряд альтернатив. Вы можете использовать предложенную оптимизацию (отключение отслеживания изменений), а затем можете просто добавлять вещи как обычно.

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

Другие вопросы по тегам