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