Шаблон единицы работы
DbContext фактически является реализацией шаблона «unit of work» - после создания DbContext
все изменения, сделанные для DbSet
, затем сохраняются за один раз, когда вы вызываете SaveChanges
.
Итак, следующий вопрос, на который вам нужно ответить, чтобы правильно ответить на ваш вопрос: каков объем изменений, составляющих вашу единицу работы? Другими словами - какой набор изменений нужно внести атомарно - все удастся или все потерпят неудачу?
Практический (если пример этого - скажем, у вас есть конечная точка API, которая предоставляет операцию, позволяющую клиенту отправить заказ. Контроллер использует OrderService для отправки заказа, а затем InventoryService для обновления инвентаря, связанного с элементами в заказе. Если каждая служба имеет свой собственный DbContext, у вас есть риск, что OrderService удастся сохранить отправку заказа, но InventoryService не сможет сохранить обновление инвентаризации.
Внедрение зависимости
Для борьбы с этим распространенным шаблоном является создание контекста для каждого запроса, позволяющая вашему контейнеру IoC создавать и удалять контекст, а также делать его доступным для внедрения в службы по запросу. Это сообщение в блоге дает несколько вариантов управления DbContext и включает пример настройки Ninject для этого.
Это означает, что ваш ctor будет выглядеть так:
public ProfileService(CommService commService, AppContext context) {
_commService = commService;
_context = context;
}
И вы можете безопасно использовать этот контекст, не беспокоясь о том, как он был создан или откуда он взялся.
DbScopeFactory Медхи
Однако я предпочитаю подход к более сложным приложениям - отличная библиотека с открытым исходным кодом, описанная здесь: http://mehdi.me/ambient-dbcontext-in-ef6/. Внедрение DbContext для каждого запроса будет отлично работать для более простых приложений, но по мере того, как ваше приложение становится более задействованным (например, несколько контекстов на приложение, несколько баз данных и т. Д.), Более тонкий элемент управления, предлагаемый его IDbContextScopeFactory
, неоценим.
Отредактируйте, чтобы добавить - Плюсы и минусы инъекции и строительства
Следуя вашему комментарию с просьбой рассказать о плюсах и минусах предложенного вами подхода, я бы сказал, что в целом внедрение зависимостей (включая DbContext) является гораздо более гибким и мощным подходом, и он все же может достичь цели, гарантирующей, что ваши разработчики не должны быть связаны с управлением жизненным циклом dbcontext.
Плюсы и минусы обычно одинаковы для всех экземпляров внедрение зависимости, а не только контекст базы данных, но вот несколько конкретных проблем с построением контекста внутри службы (даже в базовой службе):
- каждая служба будет иметь свой собственный экземпляр dbcontext - это может привести к проблемам согласованности, когда ваша единица работы охватывает задачи, выполняемые несколькими службами (см. пример выше)
- Будет намного сложнее провести модульное тестирование ваших сервисов, поскольку они создают свои собственные зависимости. Внедрение dbcontext означает, что вы можете имитировать его в своих модульных тестах и протестировать функциональность без попадания в базу данных
- It introduces unmanaged state into your services - if you are using dependency injection, you want the IoC container to manage the lifecycle of services. When your service has no per-request dependencies, the IoC container will create a single instance of the service for the whole application, which means your dbcontext saved to the private member will be used for all requests/threads - this can be a big problem and should be avoided.
- (Note: this is less of an issue if you are not using DI and constructing new instances of the services within controllers, but then you are losing the benefits of DI at the controller level as well...)
- Все службы теперь заблокированы для использования одного и того же экземпляра DbContext - что, если в будущем вы решите разделить свою базу данных и некоторым службам потребуется доступ к другому DbContext? Вы бы создали две разные BaseServices? Или передать данные конфигурации, чтобы разрешить переключение базовой службы? DI позаботится об этом, потому что вы просто зарегистрируете два разных класса Context, а затем контейнер предоставит каждой службе нужный контекст.
- Вы куда-нибудь возвращаете
IQueryable
s? Если да, то вы рискуете, что IQueryable
вызовет попадание в Db даже после того, как DbContext вышел за пределы области видимости - он мог быть удален сборщиком мусора и не будет доступен.
С точки зрения разработчика, я думаю, что нет ничего проще, чем подход DI - просто укажите DbContext в своем конструкторе, а контейнер контейнера DI позаботится обо всем остальном.
Если вы используете DbContext для каждого запроса, вам даже не нужно создавать или удалять контекст, и вы можете быть уверены, что IQueryables будет разрешен в любой точке стека вызовов запроса.
Если вы используете подход Мехди, вам действительно нужно создать DbContextScope, но этот подход более уместен, если вы идете по пути шаблона репозитория и хотите явный контроль над областью контекста.
Как видите, меня гораздо меньше беспокоят вычислительные затраты на создание DbContext, когда он не нужен (насколько я могу судить, это довольно низкая стоимость, пока вы действительно не используете его для попадания в базу данных), и более обеспокоен об архитектуре приложения, которая допускает модульное тестирование и отделение от зависимостей.
12.09.2016