Комментарии 22
Совет №3. Массовое обновление данных с помощью DetectChanges
В EF 6 добавлены методы DbSet.AddRange и DbSet.RemoveRange, которые решают данную проблему.
Кстати, иногда для ускорения вставки так же рекомендуют отключить
DbContext.Configuration.ValidateOnSaveEnabled
, но это может привести к соответствующим результатам, поэтому этим стоит пользоваться, только если проверка данных была предварительно произведена.Дополнительно:
Упущен контроль времени жизни контекста данных.
Одно из общих правил работы с контекстом данных состоит в том, что контекст необходимо освобождать, как только он перестает быть необходимым.
Т.е. в данном случае необходимо воспользоваться оператором
var dbContext = new AppDbContext();
Упущен контроль времени жизни контекста данных.
Одно из общих правил работы с контекстом данных состоит в том, что контекст необходимо освобождать, как только он перестает быть необходимым.
Т.е. в данном случае необходимо воспользоваться оператором
using
:using (var dbContext = new AppDbContext()){
...
}
Сильно зависит от того, как вы используете LazyLoading. Внезапно получить ObjectDisposedException при работе с объектом, полученным из БД, не особо приятно.
Совет здесь один — освобождайте явно контекст, когда он вам 100% больше не нужен.
Совет здесь один — освобождайте явно контекст, когда он вам 100% больше не нужен.
Согласен, это возможно в случае наличия Navigation Properties.
Однако, если воспользоваться методом, который описан в коде, то мы полностью возлагаете контроль времени жизни на .NET Framework. Что за частую означает, что контекст не будет освобожден достаточно продолжительный промежуток времени.
Хотел бы отметить, что наличие свойств с отложенной загрузкой вне рамок конкретного репозитория, или какого-то дополнительного уровня (например уровня сервисов), может привести к неожиданному поведению в процессе дальнейшего использования.
Зачастую возникают случаи, когда пользователь ожидает 1 вызов базы данных, в то время, как получает N+1 вызов (например, к этому может привести обращение к свойству навигации, которое является коллекцией каких-то элементов, в цикле
Общими рекомендациями при работе со свойствами навигации являются следующие:
1. Не использовать отложенную загрузку или перенести ее включение на этап оптимизации.
2. Не использовать свойства навигации выше определенного уровня абстракции (например, уровня репозитория или сервисов). При этом подключать необходимые свойства навигации при помощи методов Explicit loading или Eager loading (при помощи метода
3. Использовать DbContext, время жизни которого есть возможность контролировать (например, использовать для этого IoC контейнер).
Однако, если воспользоваться методом, который описан в коде, то мы полностью возлагаете контроль времени жизни на .NET Framework. Что за частую означает, что контекст не будет освобожден достаточно продолжительный промежуток времени.
Хотел бы отметить, что наличие свойств с отложенной загрузкой вне рамок конкретного репозитория, или какого-то дополнительного уровня (например уровня сервисов), может привести к неожиданному поведению в процессе дальнейшего использования.
Зачастую возникают случаи, когда пользователь ожидает 1 вызов базы данных, в то время, как получает N+1 вызов (например, к этому может привести обращение к свойству навигации, которое является коллекцией каких-то элементов, в цикле
foreach
).Общими рекомендациями при работе со свойствами навигации являются следующие:
1. Не использовать отложенную загрузку или перенести ее включение на этап оптимизации.
2. Не использовать свойства навигации выше определенного уровня абстракции (например, уровня репозитория или сервисов). При этом подключать необходимые свойства навигации при помощи методов Explicit loading или Eager loading (при помощи метода
Include
).3. Использовать DbContext, время жизни которого есть возможность контролировать (например, использовать для этого IoC контейнер).
У меня есть вопрос:
Пусть у вас есть сущность Person, пусть она имет следующие атрибуты: Id, Name, Age, Phone, etc.
Положим также, что вам нужно иметь метод который умеет фильтровать такие сущности по некоторым из перечисленых атрибутов.
Как будет выглядеть метод репозитория, который реализует данную функциональность?
Будет ли он просто возвращать dbContext.People?
Пусть у вас есть сущность Person, пусть она имет следующие атрибуты: Id, Name, Age, Phone, etc.
Положим также, что вам нужно иметь метод который умеет фильтровать такие сущности по некоторым из перечисленых атрибутов.
Как будет выглядеть метод репозитория, который реализует данную функциональность?
Будет ли он просто возвращать dbContext.People?
Я бы сделал что-то типа:
Смысл в том, что метод делает ровно то, что вы от него ждете — выдает вам из БД фильтрованный список записей. В параметры вы можете ему передавать поля фильтрации, пейджинга, сортировки и т.п. — все будет делаться в этом методе.
public Person[] FindPeople(PersonFilter filter)
{
using(var dbContext = new AppDbContext())
{
var query = dbContext.People;
if(filter.Age != null)
query = query.Where(p=>p.Age == filter.Age);
//Остальная фильтрация
...
return query.ToArray();
}
}
Смысл в том, что метод делает ровно то, что вы от него ждете — выдает вам из БД фильтрованный список записей. В параметры вы можете ему передавать поля фильтрации, пейджинга, сортировки и т.п. — все будет делаться в этом методе.
AsQuerable() еще нужно не забыть вызвать:
var query = dbContext.People.AsQuerable();
Этож тормозить будет
Что будет тормозить? Это же пример подхода к построению репозитория. Вся его прелесть в том, что вы можете оптимизировать этот метод фильтрации как угодно, а потребители этого метода ничего не заметят.
Получение всех объектов Person со всеми полями, а потом компиляция Expression Tree в метод.
Где вы здесь видите получение ВСЕХ объектов Person (всех что есть в БД)? В этом методе сначала строится LINQ запрос (query), а затем по нему вытянутся из БД только нужные Person-ы. Или вы говорите о том, что само построение SQL запроса из ExpressionTree будет тормозить?
Вопрос в том, что вам, возможно, не нужны ВСЕ поля класса Person в результате — тогда можно выдавать какой-нибудь PersonView[] с нужными полями.
Вопрос в том, что вам, возможно, не нужны ВСЕ поля класса Person в результате — тогда можно выдавать какой-нибудь PersonView[] с нужными полями.
А проекцию как сделать? Без нее будет тормозить.
Также вы предлагаете для каждого класса Entity написать руками EntityFilter и один метод, который этот EntityFilter применяет к IQueryable? Не проще пользоваться IQueryable?
Также вы предлагаете для каждого класса Entity написать руками EntityFilter и один метод, который этот EntityFilter применяет к IQueryable? Не проще пользоваться IQueryable?
существует несколько способов реализации репозиториев. В общем случае, можно использовать:
и в коде реализовать как:
Но лучше этого избегать и рассмотреть specification pattern
public class Repository<T> {
IList<T> LoadAllMatching<TOrderBy>(Expression<Func<T, bool>> whereCondition, Expression<Func<T, TOrderBy>> orderBy, bool desc);
}
и в коде реализовать как:
var query = GetQuery<T>().Where(whereCondition);
query = desc ? query.OrderByDescending(orderBy) : query.OrderBy(orderBy);
Но лучше этого избегать и рассмотреть specification pattern
Какой в этом смысл? Почему нельзя просто пользоваться IQueryable?
Зачем вам тогда репозиторй вообще? EF сам по себе представляет абстракцию, с которой вы работаете. Если вы возвращаете из репозитория IQuerable, то репозиторий по сути превращается в некий helper для DbContext.
С тем же успехом можно просто методы в ваш контекст добавлять и работать с ними.
Также проблема IQuerable в том, что вы перекладываете логику (включая всевозможные оптимизации) построения реального запроса к БД на потребителя вашего метода, т.к. он может накрутить на ваш IQuerable еще кучу всякой логики.
С тем же успехом можно просто методы в ваш контекст добавлять и работать с ними.
Также проблема IQuerable в том, что вы перекладываете логику (включая всевозможные оптимизации) построения реального запроса к БД на потребителя вашего метода, т.к. он может накрутить на ваш IQuerable еще кучу всякой логики.
В этом-то и вопрос, стоит ли вообще использовать паттерн Repository при использовании EF?
EF с одной стороны реализует Unit of Work через класс DbContext, с другой стороны реализует Specification(ну или их аналог) через предикаты в IQueryable.
И тут мы оказываемся на перепутье: с одной сторны не плохо было бы изолировать логику работы с БД в Repository, с другой стороны это уже сделал за нас EF,
Получается в Repository стоит добавлять только следующие методы:
1. Часто повторяющиеся, не однострочные, например какой-нибдуь метод которые делает пару join'ов и который встречается больше одного раза.
2. Использующие не типизированный код, например, вызов каких-нибудь нетривильных SQL-запросов
А все остальные запросы к EF контексту оставить как есть, т.е. запрос вида var person = dbContext.People.First(p=>p.Id == id); использовать напрямую.
Что думаете?
EF с одной стороны реализует Unit of Work через класс DbContext, с другой стороны реализует Specification(ну или их аналог) через предикаты в IQueryable.
И тут мы оказываемся на перепутье: с одной сторны не плохо было бы изолировать логику работы с БД в Repository, с другой стороны это уже сделал за нас EF,
Получается в Repository стоит добавлять только следующие методы:
1. Часто повторяющиеся, не однострочные, например какой-нибдуь метод которые делает пару join'ов и который встречается больше одного раза.
2. Использующие не типизированный код, например, вызов каких-нибудь нетривильных SQL-запросов
А все остальные запросы к EF контексту оставить как есть, т.е. запрос вида var person = dbContext.People.First(p=>p.Id == id); использовать напрямую.
Что думаете?
В основном согласен, в плане того, что специфические запросы реализовать в виде методов, а остальное дать на откуп пользователям, которые могут выполнить специфические запросы или целые комбинации запросов. В процессе использования будут кристаллизированны и другие необходимые методы.
Не стоит забывать о том, что Repository является дополнительным уровнем абстракции, который вы контролируете. Нет привязки к конкретному хранилищу или технологии хранения. Так же, можно вводить дополнительные ограничения, которые диктуются другими используемыми принципами проектирования, например, репозитории могут быть реализованы только для корневых объектов (aggregate root), т.е. появляется дополнительный контроль над выполнением принципов DDD.
Зачем вам тогда репозиторй вообще?
Не стоит забывать о том, что Repository является дополнительным уровнем абстракции, который вы контролируете. Нет привязки к конкретному хранилищу или технологии хранения. Так же, можно вводить дополнительные ограничения, которые диктуются другими используемыми принципами проектирования, например, репозитории могут быть реализованы только для корневых объектов (aggregate root), т.е. появляется дополнительный контроль над выполнением принципов DDD.
Я скажу так. EF — это фреймворк и это не только получение, но и сохранение данных. Как и любой фреймворк, он навязывает вам определенную архитектуру по работе с БД. В частности, паттерн UnitOfWork.
Если вы делаете общедоступным свой DbContext или методы, возвращающие IQuerable, вы подсаживаете все части своего приложения на логику работы ORM. Хотя это дает гибкость в получении и сохранении любых данных в любом месте вашего приложения, есть и весьма значительные минусы, например:
— у вас появляются похожие запросы в разных частях приложения. Приходится все равно делать что-то типа репозитория, чтобы избежать дублирования кода
— вы начинаете сохранять данные через DbContext.SaveChanges() в куче мест вашего приложения. Затем возникают сложные ситуации, где надо использовать транзакции, хранимые процедуры, определенную последовательность сохранения и т.п. Вам приходится все равно выносить всю эту логику из клиентского кода в репозиторий.
— вы понимаете, что EF — вообще отстой, делает очень неэффективные запросы, ест кучу памяти, при сохранении выдает вам какие-то странные и непонятные exception-ы и вообще начинает вас выводить из себя. А оказывается, что на него всё завязано и чтобы это переделать, надо переписать и ЗАНОВО ПРОТЕСТИРОВАТЬ 70% приложения. Вот тут вы начинаете по другому делать СЛЕДУЮЩЕЕ приложение :)
Если вы делаете общедоступным свой DbContext или методы, возвращающие IQuerable, вы подсаживаете все части своего приложения на логику работы ORM. Хотя это дает гибкость в получении и сохранении любых данных в любом месте вашего приложения, есть и весьма значительные минусы, например:
— у вас появляются похожие запросы в разных частях приложения. Приходится все равно делать что-то типа репозитория, чтобы избежать дублирования кода
— вы начинаете сохранять данные через DbContext.SaveChanges() в куче мест вашего приложения. Затем возникают сложные ситуации, где надо использовать транзакции, хранимые процедуры, определенную последовательность сохранения и т.п. Вам приходится все равно выносить всю эту логику из клиентского кода в репозиторий.
— вы понимаете, что EF — вообще отстой, делает очень неэффективные запросы, ест кучу памяти, при сохранении выдает вам какие-то странные и непонятные exception-ы и вообще начинает вас выводить из себя. А оказывается, что на него всё завязано и чтобы это переделать, надо переписать и ЗАНОВО ПРОТЕСТИРОВАТЬ 70% приложения. Вот тут вы начинаете по другому делать СЛЕДУЮЩЕЕ приложение :)
— у вас появляются похожие запросы в разных частях приложения. Приходится все равно делать что-то типа репозитория, чтобы избежать дублирования кода
Не придется, обычных функций достаточно и нет необходимости прятать IQueryable.
— вы начинаете сохранять данные через DbContext.SaveChanges() в куче мест вашего приложения. Затем возникают сложные ситуации, где надо использовать транзакции, хранимые процедуры, определенную последовательность сохранения и т.п. Вам приходится все равно выносить всю эту логику из клиентского кода в репозиторий.
Не придется. Транзакции — это бизнес-функционал. Если транзакции тащить в репозитории, то вся БЛ переедет в репозитории.
— вы понимаете, что EF — вообще отстой, делает очень неэффективные запросы, ест кучу памяти, при сохранении выдает вам какие-то странные и непонятные exception-ы и вообще начинает вас выводить из себя. А оказывается, что на него всё завязано и чтобы это переделать, надо переписать и ЗАНОВО ПРОТЕСТИРОВАТЬ 70% приложения. Вот тут вы начинаете по другому делать СЛЕДУЮЩЕЕ приложение :)
А как тут поможет репозиторий? На него не будет завязано? Или вы думаете что можно «просто» поменять EF на рукопашные запросы? А если у вас LL использовался?
Ответили выше.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Entity Framework Code First на практике