Я читал много статей, объясняющих, как настроить Entity Framework DbContext
так, чтобы только одна создавалась и использовалась для каждого веб-запроса HTTP с использованием различных структур DI.
Почему это хорошая идея в первую очередь? Какие преимущества вы получаете, используя этот подход? Есть ли определенные ситуации, когда это было бы хорошей идеей? Есть ли вещи, которые вы можете сделать с помощью этой техники, которые вы не можете сделать при создании экземпляра DbContext
s для каждого вызова метода репозитория?
Ответы:
Давайте начнем с того, что повторю Йену: иметь сингл
DbContext
для всего приложения - плохая идея. Единственная ситуация, в которой это имеет смысл, - это когда у вас есть однопоточное приложение и база данных, которая используется только этим единственным экземпляром приложения. ОнDbContext
не является поточно-ориентированным и, посколькуDbContext
данные кэшируются, довольно скоро устареет. Это доставит вам массу неприятностей, когда несколько пользователей / приложений будут работать с этой базой данных одновременно (что, конечно, очень часто). Но я ожидаю, что вы уже знаете это и просто хотите знать, почему бы просто не внедрить новый экземпляр (то есть с временным образом жизни) в того,DbContext
кто в этом нуждается. (для получения дополнительной информации о том, почему одиночныйDbContext
- или даже контекст на поток - плохо, прочитайте этот ответ ).Позвольте мне начать с того, что регистрация в
DbContext
качестве переходного процесса может работать, но, как правило, вы хотите иметь один экземпляр такой единицы работы в определенной области. В веб-приложении может оказаться целесообразным определить такую область на границах веб-запроса; таким образом, на веб-запрос стиль жизни. Это позволяет вам позволить целому набору объектов работать в одном контексте. Другими словами, они работают в рамках одной бизнес-операции.Если у вас нет цели, чтобы набор операций работал в одном и том же контексте, в этом случае переходный образ жизни хорош, но есть несколько вещей, на которые стоит обратить внимание:
_context.SaveChanges()
(в противном случае изменения будут потеряны). Это может усложнить ваш код и добавить к нему вторую ответственность (ответственность за контроль контекста) и является нарушением принципа единой ответственности .DbContext
] никогда не покидают область действия такого класса, потому что они не могут использоваться в экземпляре контекста другого класса. Это может сильно усложнить ваш код, потому что, когда вам нужны эти объекты, вам нужно загрузить их снова по id, что также может вызвать проблемы с производительностью.DbContext
реализуетIDisposable
, вы, вероятно, все еще хотите избавиться от всех созданных экземпляров. Если вы хотите сделать это, у вас есть два варианта. Вы должны располагать их в том же методе сразу после вызоваcontext.SaveChanges()
, но в этом случае бизнес-логика становится владельцем объекта, который передается извне. Второй вариант заключается в удалении всех созданных экземпляров на границе запроса Http, но в этом случае вам все еще требуется некоторая область видимости, чтобы сообщить контейнеру о необходимости удаления этих экземпляров.Другой вариант - вообще не вводить
DbContext
. Вместо этого вы вводите,DbContextFactory
который может создать новый экземпляр (я использовал этот подход в прошлом). Таким образом, бизнес-логика явно контролирует контекст. Если может выглядеть так:Плюсом этого является то, что вы управляете жизнью
DbContext
явно, и это легко настроить. Это также позволяет вам использовать один контекст в определенной области, который имеет явные преимущества, такие как выполнение кода в одной бизнес-транзакции и возможность передавать объекты, поскольку они происходят из одного и того жеDbContext
.Недостатком является то, что вам придется переходить
DbContext
от метода к методу (который называется инъекцией метода). Обратите внимание, что в некотором смысле это решение аналогично подходу «scoped», но теперь область действия контролируется в самом коде приложения (и, возможно, повторяется много раз). Это приложение, которое отвечает за создание и распоряжение единицей работы. Так какDbContext
объект создается после построения графа зависимостей, добавление в конструктор находится вне пределов видимости, и вам необходимо перейти к внедрению метода, когда вам нужно передать контекст из одного класса в другой.Инъекция метода не так уж и плоха, но когда бизнес-логика усложняется и в нее вовлекается больше классов, вам придется передавать ее из метода в метод и из класса в класс, что может сильно усложнить код (я видел это в прошлом). Однако для простого приложения этот подход подойдет.
Из-за недостатков этот фабричный подход для больших систем может быть полезен другой подход, в котором вы позволяете контейнеру или коду инфраструктуры / Composition Root управлять единицей работы. Это стиль, о котором ваш вопрос.
Позволяя контейнеру и / или инфраструктуре обрабатывать это, код вашего приложения не загрязняется необходимостью создавать, (необязательно) фиксировать и утилизировать экземпляр UoW, что делает бизнес-логику простой и чистой (просто единая ответственность). Есть некоторые трудности с этим подходом. Например, вы фиксировали и утилизировали экземпляр?
Распоряжение единицей работы может быть сделано в конце веб-запроса. Однако многие люди ошибочно полагают, что это также место для фиксации единицы работы. Однако в этот момент в приложении вы просто не можете точно определить, что единица работы действительно должна быть зафиксирована. Например, если код бизнес-уровня выдал исключение, которое было перехвачено выше стека вызовов, вы определенно не хотите совершать .
Реальное решение снова состоит в том, чтобы явно управлять некоторой областью действия, но на этот раз сделайте это внутри корня композиции. Абстрагируясь от всей бизнес-логики, лежащей в основе шаблона команда / обработчик , вы сможете написать декоратор, который можно обернуть вокруг каждого обработчика команды, который позволяет это делать. Пример:
Это гарантирует, что вам нужно написать этот код инфраструктуры только один раз. Любой твердотельный DI-контейнер позволяет вам настроить такой декоратор так, чтобы он
ICommandHandler<T>
согласованно оборачивался вокруг всех реализаций.источник
CreateCommand<TEnity>
и универсальныйCreateCommandHandler<TEntity> : ICommandHandler<CreateCommand<TEntity>>
(и сделать то же самое для Обновления и Удалить и иметь одинGetByIdQuery<TEntity>
запрос). Тем не менее, вы должны спросить себя, является ли эта модель полезной абстракцией для операций CRUD, или она просто добавляет сложности. Тем не менее, вы можете извлечь выгоду из возможности легко добавить сквозные задачи (через декораторов) с помощью этой модели. Вам придется взвесить все за и против.TransactionCommandHandlerDecorator
? например, если декорированный класс являетсяInsertCommandHandler
классом, как он может зарегистрировать операцию вставки в контекст (DbContext в EF)?Есть две противоречивые рекомендации от Microsoft, и многие люди используют DbContexts совершенно по-разному.
Они противоречат друг другу, потому что, если ваш Запрос в значительной степени не связан с Db, то ваш DbContext сохраняется без причины. Таким образом, тратить ваш DbContext на жизнь бесполезно, пока ваш запрос просто ожидает выполнения случайных вещей ...
Так много людей, которые следуют правилу 1, имеют свои DbContexts внутри своего «шаблона репозитория» и создают новый экземпляр для каждого запроса к базе данных, поэтому X * DbContext для запроса
Они просто получают свои данные и располагают контекстом как можно скорее. Это, по мнению МНОГИХ людей приемлемой практики. Хотя это имеет преимущество в том, что вы занимаете ресурсы БД в течение минимального времени, оно явно жертвует всем, что может предложить EF UnitOfWork и Caching .
Поддержание в живых одного многоцелевого экземпляра DbContext максимизирует преимущества кэширования, но поскольку DbContext не является поточно- ориентированным, и каждый веб-запрос выполняется в своем собственном потоке, DbContext на запрос является самым длинным, который вы можете сохранить.
Таким образом, рекомендация команды EF об использовании 1 Db Context на запрос явно основана на том факте, что в веб-приложении UnitOfWork, скорее всего, будет в пределах одного запроса, и этот запрос имеет один поток. Таким образом, один DbContext на запрос подобен идеальному преимуществу UnitOfWork и Caching.
Но во многих случаях это не так. Я считаю Logging отдельным UnitOfWork, таким образом, наличие нового DbContext для Post-Request Logging в асинхронных потоках вполне приемлемо
Наконец, получается, что время жизни DbContext ограничено этими двумя параметрами. UnitOfWork and Thread
источник
Ни один ответ здесь на самом деле не отвечает на вопрос. ОП не спрашивал о дизайне DbContext для одного приложения / приложения, он спрашивал о дизайне запроса для каждого веб-сайта и о том, какие потенциальные выгоды могут существовать.
Я буду ссылаться на http://mehdi.me/ambient-dbcontext-in-ef6/, так как Мехди - фантастический ресурс:
Имейте в виду, что есть и минусы. Эта ссылка содержит много других ресурсов для чтения на эту тему.
Просто разместите это на тот случай, если кто-то еще наткнется на этот вопрос и не будет поглощен ответами, которые на самом деле не касаются вопроса.
источник
Я почти уверен, что это потому, что DbContext не является потокобезопасным. Так что делиться вещами никогда не будет хорошей идеей.
источник
Одна вещь, которая на самом деле не рассматривается в вопросе или обсуждении, это то, что DbContext не может отменить изменения. Вы можете отправлять изменения, но вы не можете очистить дерево изменений, поэтому, если вы используете контекст для каждого запроса, вам не повезло, если вам по какой-то причине нужно выбросить изменения.
Лично я создаю экземпляры DbContext, когда это необходимо - обычно присоединяемые к бизнес-компонентам, которые могут при необходимости воссоздавать контекст. Таким образом, я контролирую процесс, а не навязываю мне один экземпляр. Мне также не нужно создавать DbContext при каждом запуске контроллера, независимо от того, используется ли он на самом деле. Затем, если я все еще хочу иметь экземпляры для каждого запроса, я могу создать их в CTOR (через DI или вручную) или создать их по мере необходимости в каждом методе контроллера. Лично я обычно использую последний подход, чтобы избежать создания экземпляров DbContext, когда они на самом деле не нужны.
Это зависит от того, под каким углом ты смотришь на это тоже. Для меня экземпляр на запрос никогда не имел смысла. Действительно ли DbContext входит в Http-запрос? С точки зрения поведения, это не то место. Ваши бизнес-компоненты должны создавать ваш контекст, а не запрос Http. Затем вы можете создавать или отбрасывать свои бизнес-компоненты по мере необходимости и никогда не беспокоиться о времени существования контекста.
источник
Я согласен с предыдущими мнениями. Хорошо сказать, что если вы собираетесь делиться DbContext в однопоточном приложении, вам потребуется больше памяти. Например, моему веб-приложению в Azure (один очень маленький экземпляр) требуется еще 150 МБ памяти, и у меня около 30 пользователей в час.
Вот реальный пример изображения: приложение было развернуто в 12 вечера
источник
Что мне нравится в этом, так это то, что он выравнивает единицу работы (в том виде, в каком ее видит пользователь, то есть отправку страницы) с единицей работы в смысле ORM.
Таким образом, вы можете сделать передачу всей страницы транзакционной, чего нельзя было бы сделать, если бы вы представляли методы CRUD при каждом создании нового контекста.
источник
Другая недооцененная причина не использовать одноэлементный DbContext, даже в однопоточном однопользовательском приложении, заключается в используемом им шаблоне карты идентификации. Это означает, что каждый раз, когда вы извлекаете данные, используя запрос или по идентификатору, он будет хранить извлеченные экземпляры сущности в кэше. В следующий раз, когда вы получите тот же объект, он предоставит вам кэшированный экземпляр объекта, если он доступен, с любыми изменениями, которые вы сделали в том же сеансе. Это необходимо, чтобы метод SaveChanges не заканчивался несколькими экземплярами сущностей одной и той же записи базы данных; в противном случае контекст должен был бы каким-то образом объединить данные из всех этих экземпляров сущности.
Причиной проблемы является то, что одноэлементный DbContext может стать бомбой замедленного действия, которая может в конечном итоге кэшировать всю базу данных + накладные расходы объектов .NET в памяти.
Есть способы обойти это поведение, используя только запросы Linq с
.NoTracking()
методом расширения. Также в наши дни компьютеры имеют много оперативной памяти. Но обычно это не желаемое поведение.источник
Еще одна проблема, на которую следует обратить особое внимание в Entity Framework, - это использование комбинации создания новых объектов, отложенной загрузки и последующего использования этих новых объектов (из того же контекста). Если вы не используете IDbSet.Create (против только что нового), отложенная загрузка этого объекта не будет работать, когда его извлекают из контекста, в котором он был создан. Пример:
источник