Почему DbContext для каждого запроса? - PullRequest
3 голосов
/ 09 февраля 2020

Существуют и другие подобные вопросы SO, но ни один из них действительно не отвечает на мой конкретный c вопрос:

Если для каждого запроса создается контроллер MVC, то в чем преимущество DbContext? -запрос (при условии, что я ввожу его в контроллер)? Разве переходный процесс не достиг бы того же результата?

Например, скажем, у меня есть форма ввода / редактирования данных - у контроллера будет метод 'get' для извлечения сущности, которая будет отредактирована из БД, и 'post' метод для сохранения обратно в БД (это потребует повторного извлечения сущности, обновления измененных значений, затем SaveChanges). Get и Post - это отдельные запросы, поэтому я не вижу, как DbContext для каждого запроса помогает здесь.

Чего мне не хватает? В каком сценарии ios полезен DbContext для каждого запроса?

Ответы [ 4 ]

3 голосов
/ 09 февраля 2020

В дополнение к ответу @ haim770s:

Почему DbContext для каждого запроса?

Одна из причин, по которой Transient может не подходить, заключается в том, что вы Вы получите новый DbContext для каждого объекта службы / репозитория / команды / запроса или объекта UnitOfWork, который вы создаете или запрашиваете.

Если вы каскадируете некоторые обновления, обеспечивая 1 DbContext для запроса, что упрощает откат все эти обновления, например, как транзакция или UnitOfWork - за запрос.

3 голосов
/ 09 февраля 2020

Разве переходный процесс не приведет к тому же результату?

Да, но это только в том случае, если вы точно знаете, что единственный вызов Resolve<DbContext>() находится в вашем контроллере (желательно через инжекцию конструктора) ), и оттуда экземпляр передается на другие уровни.

Очевидно, что это не всегда так.

В вашем коде всегда могут быть другие "точки разрешения" (MVC фильтры, сервисные уровни, вызываемые косвенно, или даже код, который использует Io C в режиме Service Locator ), и в этих случаях обычно требуется обеспечить использование одного и того же экземпляра DbContext во всем HTTP-запрос.

1 голос
/ 10 февраля 2020

В простейших примерах, когда у вас есть метод GET и POST в контроллере, и ничего более, нет никакой разницы между использованием Transient и Per-Request. Когда приходит Per-Request, ваш контроллер переключается на другие классы для выполнения соответствующей работы. Типичным примером является шаблон репозитория, но на самом деле также применим любой класс службы / помощника, который централизует общее поведение. Если DbContext является Transient, и вы вызываете одну из этих служб, передавая объект, который контроллер извлек из DbContext, и эти службы обращаются к DbContext, они будут использовать экземпляр DbContext, отличный от того, который сделал Контроллер. Это может привести к довольно раздражающим проблемам с отслеживанием сущностей и попытками вставить повторяющиеся строки, потому что экземпляр DbContext контроллера не отслеживает сущности, которые могли быть загружены другим DbContext и связаны с сущностью, отслеживаемой экземпляром контроллера. Используя экземпляр для каждого запроса, любой связанный класс, который запрашивает DbContext, получит тот же экземпляр, который устраняет эти проблемы.

Даже если все операции находятся в некотором роде c друг от друга, наличие одного экземпляра означает, что все эти изменения будут зафиксированы или откатаны вместе. Хотя это, как правило, желаемое поведение, само по себе это может привести к неожиданным проблемам, поскольку возникает вопрос о том, когда следует вызывать SaveChanges? Когда у вас есть контроллер, вызывающий различные распространенные методы, и DbContext ограничивается для каждого запроса, если любой из этих методов вызывает SaveChanges, все, что было сделано до этого момента, будет пытаться зафиксировать в БД. Таким образом, вы можете столкнуться с проблемами, когда вы вызываете 3 общих сервисных метода, каждый из которых вызывает SaveChanges, первые 2 успешно завершаются без проблем, а 3-й сбой. Как правило, вы ожидаете, что Контроллер будет иметь последнее слово для выполнения фиксации и обработки исключений. В этих случаях может быть полезно использовать Единицу Работы, чтобы «заключить» в DbContext и гарантировать, что решение о фиксации или откате DbContext поддерживается на самом высоком уровне операции. Таким образом, UoW может быть ограничено в начале запроса (т. Е. Метод контроллера), а вспомогательные службы и т. Д. c. получить ссылку на DbContext от UoW, но контроллер получает последнее слово, если / когда вызывается UoW SaveChanges.

1 голос
/ 09 февраля 2020

Scoped DbContext против переходного DbContext - Разница

Scoped DbContext Неявно разделяет один экземпляр вашего DbContext в объеме запроса. Это означает, что во время существования запроса все классы, которые пытаются получить экземпляр DbContext, получают общий отдельный экземпляр DbContext.

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

Transient DbContext означает, что каждый класс, который пытается получить экземпляр DbContext, получает новый экземпляр fre sh, не разделяемый неявно каким-либо другим классом.

Для Например, если по какой-либо причине действие, фильтр действий или несколько классов бизнес-логики c пытаются получить экземпляр вашего DbContext, все они получают fre sh новых различных экземпляров.

Так что разница теперь ясна. Правильно? Давайте рассмотрим некоторые соображения при использовании Scoped или Transient.

Соображения - Выберите Scoped DbContext или Transient DbContext?

По умолчанию - При использовании метода AddDbContext, Scoped используется по умолчанию, если вы не указываете срок службы.

Производительность - С точки зрения производительности Scoped лучше, потому что создает меньше экземпляров. Но если вы не создадите несколько экземпляров, различий не будет. Например, если вы самостоятельно внедрили шаблон Unit of Work и создаете экземпляр DbContext на уровне обслуживания для каждого запроса, нет разницы между областью видимости и переходным процессом.

Неявно общий доступ - Поскольку Scoped является неявным, но при этом упрощается совместное использование и улучшается производительность, но его следует использовать с осторожностью Я поделюсь несколькими сценариями ios, которые Scoped могут потерпеть неудачу и требуют большего внимания:

  • Attach - Простой пример отказа Scoped DbContext - это случай, когда вы пытаетесь Attach сущность дважды, не зная, что контекст БД был передан другому классу (без вашей информации), и предыдущий класс уже загрузил сущность в контексте. Попытка присоединения завершится неудачей.

  • SaveChanges - Еще один пример сбоя Scoped DbContext с возможностью непреднамеренного SaveChanges. Один класс может изменить контекст, но по какой-то причине не хочет сохранять изменения, тогда, если класс не откатывает все изменения, другой класс позже может вызвать SaveChanges и непреднамеренно также сохранить эти нежелательные изменения.

  • Многопоточный - Если вы используете несколько потоков для доступа к DbContext в одном запросе, вам нужно быть более осторожным. Согласно документации, любой код, который явно выполняет несколько потоков параллельно, должен гарантировать, что экземпляры DbContext никогда не будут доступны одновременно. Это означает, что вам нужно использовать Transient или если вы зарегистрировали контекст как Scoped , то вам нужно создать области (используя IServiceScopeFactory) для каждого потока. Вы можете взглянуть на Неявное совместное использование экземпляров DbContext между несколькими потоками посредством внедрения зависимостей .

...