Entity Framework (EF) исключение OutOfMemoryException

#c# #.net #entity-framework #entity-framework-4 #out-of-memory

#c# #.net #entity-framework #entity-framework-4 #нехватка памяти

Вопрос:

У меня более 67000 записей, поступающих в мою систему из другого источника. После применения бизнес-правил к этим записям я должен сохранить их в базе данных. Для этого я использую следующий код:

         using (var context = new MyEntities())
        {
            var importDataInfo = context.ImportDataInfoes.First(x => x.ID == importId);

            importedRecords.ForEach(importDataInfo.ValuationEventFulls.Add);

            context.SaveChanges();
        }
  

После выполнения кода я получаю следующую ошибку (исключение OutOfMemoryException)

     Error in executing code|Exception of type 'System.OutOfMemoryException' was thrown.*   at System.Data.Mapping.Update.Internal.KeyManager.<WalkGraph>d__5.MoveNext()
   at System.Data.Mapping.Update.Internal.KeyManager.GetPrincipalValue(PropagatorResult result)
   at System.Data.Mapping.Update.Internal.UpdateCompiler.GenerateValueExpression(EdmProperty property, PropagatorResult value)
   at System.Data.Mapping.Update.Internal.UpdateCompiler.BuildSetClauses(DbExpressionBinding target, PropagatorResult row, PropagatorResult originalRow, TableChangeProcessor processor, Boolean insertMode, Dictionary`2amp; outputIdentifiers, DbExpressionamp; returning, Booleanamp; rowMustBeTouched)
   at System.Data.Mapping.Update.Internal.UpdateCompiler.BuildInsertCommand(PropagatorResult newRow, TableChangeProcessor processor)
   at System.Data.Mapping.Update.Internal.TableChangeProcessor.CompileCommands(ChangeNode changeNode, UpdateCompiler compiler)
   at System.Data.Mapping.Update.Internal.UpdateTranslator.<ProduceDynamicCommands>d__0.MoveNext()
   at System.Linq.Enumerable.<ConcatIterator>d__71`1.MoveNext()
   at System.Data.Mapping.Update.Internal.UpdateCommandOrderer..ctor(IEnumerable`1 commands, UpdateTranslator translator)
   at System.Data.Mapping.Update.Internal.UpdateTranslator.ProduceCommands()
   at System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager stateManager, IEntityAdapter adapter)
   at System.Data.EntityClient.EntityAdapter.Update(IEntityStateManager entityCache)
   at System.Data.Objects.ObjectContext.SaveChanges(SaveOptions options)
  

Я использую EF 4.0.

Мой вопрос в том, есть ли ограничение на количество сохраняемых записей? Как лучше всего сохранять большое количество записей (сохранять их фрагментами? как насчет транзакций?).

Заранее всем спасибо.

Ответ №1:

Вероятно, вы захотите отправлять эти данные пакетами, вероятно, по 1024 записи за раз.

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

Распределенные транзакции могут быть применены только к серверу, на котором запущена служба Microsoft Distributed Transaction Coordinator (MS-DTC). При работе с распределенными транзакциями наблюдается заметное снижение производительности.

Комментарии:

1. Спасибо за ответ. — Да, я изменил свой код, чтобы проверить, работает ли он с фрагментами записей, и это действительно работает, но мой вопрос в том, что я ничего не читал об ограничении количества записей в SaveChanges () в EF. Вы слышали от MS об этом ограничении?

2. @Vlad Bezden ограничение не связано конкретно с EF, это просто часть работы с . Сетевая технология. Когда вы вводите 67 тысяч записей ETL, у вас, вероятно, более 200 тысяч объектов в памяти. У вас есть 67 тыс. исходных записей, у вас есть 67 тыс. объектов EF, и каждый, скорее всего, создает экземпляры нескольких объектов отслеживания состояния внутри контекста для каждого объекта, так что прямо сейчас уже 67 тыс. 67 тыс. 67 тыс., я не удивлюсь, что по пути также создается пара других вспомогательных объектов. EF работает по шаблону отложенного запроса, он не выполняет реальной работы, пока вы не вызовете SaveChanges, вот почему возникает ООМ.

3. У меня похожая проблема. Не ясно, как следует изменять фрагмент. Было бы неплохо увидеть в качестве обновления исходный вопрос. У меня действительно есть существующий код, где есть . Добавьте внутренний цикл foreach и сохраняйте изменения сразу после цикла. Неясно, включает ли SaveChanges в цикл, всегда ли понятно, что было добавлено через .Add

4. @MicMit вы хотите внести пакетные изменения на сервер. Итак, для X записей, скажем, 1024, вы хотите вызывать SaveChanges каждые 1024 записи. Если вы используете цикл for, который вы могли бы легко написать if(i24 == 0) context.SaveChanges() , тогда у вас все равно будет 1 последний вызов для сохранения изменений вне цикла, поскольку у вас, скорее всего, не будет пакета идеального размера последнего. При работе с большими наборами записей вы также можете захотеть заглянуть в yield return оператор и поработать с перечислимыми, чтобы не загружать целые наборы записей в память.

5. @Chris По какой-то причине это не имело никакого значения для пакета из 10. Знаете ли вы какие-либо ссылки на примеры кода для подхода, который вы предложили. Вы на самом деле имели в виду переопределение / перезапись сохраненных изменений или дополнений.

Ответ №2:

В общем случае .NET ограничивается адресацией 2 ГБ памяти в одной коллекции или другом объекте. Это потому, что даже в 64-разрядной среде индексаторы используют 32-разрядные целые числа (с максимальным значением 2 миллиарда и изменениями). Даже для простых типов данных, таких как целые числа, размер одного int означает, что в одном массиве может храниться только 500 миллионов целых чисел. Для больших типов значений, таких как structures, максимальное количество элементов коллекции становится действительно маленьким.

Если вы работаете в 32-разрядной среде, такой как Windows XP, существуют еще более низкие ограничения; максимальный объем памяти для ВСЕЙ ПРОГРАММЫ не может превышать 2 ГБ. Это накладывает некоторые довольно высокие ограничения на ETL, подобный вашему, и я бы совсем не удивился, что вашей программе не хватает памяти, пытаясь обработать 67k записей в памяти одновременно.

Решение состоит в том, чтобы обрабатывать записи меньшими пакетами, если вы можете. Попробуйте создать инструкцию на основе идентификатора, в которой вы возвращаете 100 лучших записей, идентификатор которых (надеюсь, автоматически сгенерированный) больше, чем самый большой идентификатор, который вы уже извлекли. Как только запись обработана, удалите ее (или просто удалите ее, и пусть GC выполняет свою работу).

Комментарии:

1. Спасибо за ответ. Я получаю OOME при вызове метода SaveChanges. У меня нет никаких проблем с .NET-кодом в моем приложении, я протестировал обработку записей импорта с > 1 000 000, и система работает нормально, сбой происходит только при вызове SaveChanges в EF

2. Вероятно, потому, что ORM, подобные EF, должны определять, что изменилось, чтобы они могли создать эффективную инструкцию UPDATE. Для этого требуется снова извлечь запись и сохранить изменения в некоторой коллекции, которую она будет использовать для генерации обновления. Кроме того, строки, которые формируют команды обновления, представляют собой наборы символов, и при умножении 67 000 на a до пары тысяч символов за обновление можно легко достичь предела длины строки. Снова попробуйте установить «размер пакета» для входящих и исходящих записей, что ограничит объем входящих данных и размер отправляемых команд.

3. Извините за позднее вступление: это вызов SaveChanges (), который вызывает OOME — так что, если я буду чаще вызывать SaveChanges, я смогу минимизировать риск этого? У меня есть цикл foreach, но я обрабатывал ВСЕ элементы перед сохранением, тогда как ничто не мешает мне сохранять каждый элемент по отдельности.