Как избежать переполнения памяти при запросе больших наборов данных с помощью Entity Framework и LINQ

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

public List<LocalDataObject> GetData(int start, int end);

В базе данных выполняется запрос с использованием LINQ to EF, и вызывающий класс может затем перебирать данные. Но так как другие классы не имеют доступа к объектам в EF, мне нужно выполнить операцию "ToList()" в запросе и путем выбора полного набора данных в память.

Что произойдет, если этот набор ОЧЕНЬ большой (10 с-100 ГБ)?

Есть ли более эффективный способ выполнения итераций и по-прежнему поддерживать свободную связь?

Ответ 1

Правильный способ работы с большими наборами данных в инфраструктуре Entity:

  • Используйте объекты EFv4 и POCO - это позволит обмениваться объектами с верхним уровнем без введения зависимости от структуры Entity
  • Отключить создание прокси/ленивую загрузку, чтобы полностью отключить объект POCO из контекста объекта.
  • Expose IQueryable<EntityType>, чтобы позволить верхнему уровню более точно определять запрос и ограничивать количество записей, загруженных из базы данных.
  • При экспонировании IQueryable установите MergeOption.NoTracking на ObjectQuery в вашем методе доступа к данным. Объединение этого параметра с отключенным созданием прокси должно приводить к тому, что не кэшированные объекты и итерация в результате запроса всегда должна загружать только один материализованный объект (без кэширования загруженных объектов).

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

Ответ 2

Насколько мне нравится EF для быстрого доступа к данным, я, вероятно, не буду использовать его для такого сценария. Имея данные такого размера, я бы выбрал хранимые процедуры, которые возвращают именно то, что вам нужно, и ничего лишнего. Затем используйте легкий DataReader для заполнения ваших объектов.

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

Кроме того, что касается управления памятью, конечно, убедитесь, что вы завершаете обработку неуправляемых ресурсов кодом в блоке using для правильного захоронение/сбор мусора.

Вы также можете рассмотреть возможность внедрения paging.

Ответ 3

Просто короткое замечание по этому вопросу:

Но поскольку другие классы не имеют доступа субъектам в EF, мне нужно выполнить операцию "ToList()" на запроса и тем, что набор данных в память.

Проблема, с которой вы сталкиваетесь здесь, по-моему, не связана с EF. Что бы вы сделали, если бы вы не использовали EF для доступа к данным, а использовали ADO.NET? "Отсутствие доступа к EF" переводит затем "Без доступа к соединению с базой данных".

Если у вас есть службы или методы, которые должны работать с большим количеством объектов, но не могут получить доступ к базе данных - либо через EF, либо через другое соединение с базой данных - вы должны загрузить данные в память, прежде чем передавать их этим сервисам/методам. Затем вы можете подумать о решениях для буферизации загруженных данных каким-то образом на жестком диске на стороне клиента, но это не имеет никакого отношения к источнику данных и тому, как их извлекать. Если вы не буферизируете и не загружаете все в память, вы ограничены доступной памятью. Я думаю, что нет никакого способа избежать этого ограничения.

Если у вас есть соединение с EF, я думаю, что решение, представленное в ответе Ladislav, является правильным, поскольку настройки и процедура, которые он описал, уменьшают EF почти до поведения простого DataReader.

Ответ 4

Я бы использовал ленивый IEnumerable и реализовал какой-то пейджинг для вас во внутренности данных.

Возможно, создайте свой собственный интерфейс IEnumerable, поэтому пользователь вашей библиотеки не соблазн сам называть ToList.

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

Ответ 5

Один из способов быть уверенным - всегда устанавливать верхний порог, который вы вернетесь, чтобы избежать гигантского набора, завершив запрос с помощью .Take(MAX_ROWS). Это может быть обходным путем или превентивным движением от плохого вызова, который снижает ваши сервисы, но лучше всего переосмыслить решение.