Набор данных большой сущности Entity, исключение из памяти
Я работаю с очень большим набором данных, примерно 2 миллиона записей. У меня есть код ниже, но получаю исключение из памяти после того, как он имеет процесс около трех партий, около 600 000 записей. Я понимаю, что, поскольку он проходит через каждую пакетную сущность фрейма, ленивые нагрузки, которые затем пытаются собрать полные 2 миллиона записей в память. Есть ли способ выгрузить пакет, который я обработал?
ModelContext dbContext = new ModelContext();
IEnumerable<IEnumerable<Town>> towns = dbContext.Towns.OrderBy(t => t.TownID).Batch(200000);
foreach (var batch in towns)
{
SearchClient.Instance.IndexMany(batch, SearchClient.Instance.Settings.DefaultIndex, "Town", new SimpleBulkParameters() { Refresh = false });
}
Примечание. Пакетный метод исходит из этого проекта: https://code.google.com/p/morelinq/
Поисковый клиент: https://github.com/Mpdreamz/NEST
Ответы
Ответ 1
Проблема заключается в том, что когда вы получаете данные из EF, на самом деле есть две копии созданных данных, одна из которых возвращается пользователю, а вторая, которую EF держит и использует для обнаружения изменений (чтобы она сохраняла изменения в база данных). EF содержит этот второй набор для времени жизни контекста и его этого набора, который выкидывает вас из памяти.
У вас есть 2 варианта решения этой проблемы.
- обновить контекст каждой партии
-
Используйте .AsNoTracking() в вашем запросе, например:
IEnumerable<IEnumerable<Town>> towns = dbContext.Towns.AsNoTracking().OrderBy(t => t.TownID).Batch(200000);
это говорит EF не сохранять копию для обнаружения изменений. Вы можете прочитать немного больше о том, что делает AsNoTracking, и о влиянии этого эффекта на мой блог: http://blog.staticvoid.co.nz/2012/4/2/entity_framework_and_asnotracking
Ответ 2
Я написал процедуру переноса, которая читает из одной БД и записывает (с небольшими изменениями в макете) в другую БД (другого типа), и в этом случае возобновление соединения для каждого пакета и использование AsNoTracking() не обрезали его для меня.
Однако следующий алгоритм решил проблему нехватки памяти:
- используйте одно соединение для чтения и одно для записи/обновления
- Читать с помощью AsNoTracking()
каждые 50 строк или около того записываются/обновляются, проверяют использование памяти, восстанавливают память + сбрасывают выходной контекст БД (и связанные таблицы) по мере необходимости:
var before = System.Diagnostics.Process.GetCurrentProcess().VirtualMemorySize64;
if (before > 800000000)
{
dbcontextOut.SaveChanges();
dbcontextOut.Dispose();
GC.Collect();
GC.WaitForPendingFinalizers();
dbcontextOut = dbcontextOutFunc();
tableOut = Dynamic.InvokeGet(dbcontextOut, outputTableName);
}