Набор данных большой сущности 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);
    }