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 - в этом ответе подробно описан ряд альтернатив. Вы можете использовать предложенную оптимизацию (отключение отслеживания изменений), а затем можете просто добавлять вещи как обычно.
Обратите внимание, что, поскольку вы добавляете много элементов одновременно, вам необходимо довольно часто воссоздавать свой контекст, чтобы остановить утечку памяти и замедление, которое вы получите.