Поиск области Ninject, которая ведет себя как InRequestScope
На моем уровне сервиса я ввел конструктор UnitOfWork
и 2 в конструкторе. В модуле работы и хранилище есть экземпляр DbContext
, который я хочу разделить между ними. Как я могу сделать это с помощью Ninject? Какую область следует рассматривать?
Я не в веб-приложении, поэтому я не могу использовать InRequestScope
.
Я пытаюсь сделать что-то подобное... и я использую DI, но мне нужно, чтобы мой UoW был Dispose
d и создан таким образом.
using (IUnitOfWork uow = new UnitOfWorkFactory.Create())
{
_testARepository.Insert(a);
_testBRepository.Insert(b);
uow.SaveChanges();
}
EDIT: Я просто хочу быть уверенным, что понимаю... после просмотра https://github.com/ninject/ninject.extensions.namedscope/wiki/InNamedScope i, хотя о моей текущей архитектуре консольного приложения, которая фактически использует Ninject.
Давайте скажем:
Класс A - класс уровня обслуживания
Класс B - это единица работы, которая принимает в параметр интерфейс (IContextFactory)
Класс C - это репозиторий, который принимает в параметр интерфейс (IContextFactory)
Идея здесь состоит в том, чтобы иметь возможность выполнять контекстные операции в 2 или более репозиториях и использовать единицу работы для применения изменений.
Класс D представляет собой контекст factory (Entity Framework), который предоставляет экземпляр (сохраняйте в контейнере) контекст, который разделяется между классом B и C (.. и будет также для других репозиториев).
Контекст factory сохраняет экземпляр в своем контейнере, поэтому я не хочу повторно использовать этот экземпляр для всего имени, так как контекст должен быть удален в конце сервисной операции. Это основная цель InNamedScope на самом деле?
Решение будет, но я не уверен, что я делаю это правильно, экземпляр служб будет трансформированным, что означает, что они на самом деле никогда не были утилизированы?
Bind<IScsContextFactory>()
.To<ScsContextFactory>()
.InNamedScope("ServiceScope")
.WithConstructorArgument(
"connectionString",
ConfigurationUtility.GetConnectionString());
Bind<IUnitOfWork>().To<ScsUnitOfWork>();
Bind<IAccountRepository>().To<AccountRepository>();
Bind<IBlockedIpRepository>().To<BlockedIpRepository>();
Bind<IAccountService>().To<AccountService>().DefinesNamedScope("ServiceScope");
Bind<IBlockedIpService>().To<BlockedIpService>().DefinesNamedScope("ServiceScope");
Ответы
Ответ 1
UPDATE: этот подход работает против тока NuGet, но полагается на аномалию в реализации InCallscope
, которая была исправлена в текущих пакетах Unstable NuGet. Я через несколько дней подберу этот ответ, чтобы отразить наилучший подход после некоторого размышления. NB, высокоуровневый способ структурирования вещей останется практически идентичным, точно будут указаны точные сведения о области Bind<DbContext>()
. (Подсказка: CreateNamedScope
в нестабильном состоянии будет работать, или можно настроить обработчик команд как DefinesNamedScope
. Причина. Я просто не хочу, чтобы я хотел иметь что-то, что хорошо сочетается с InRequestScope
)
Я настоятельно рекомендую прочитать теги интеграции Ninject.Extensions.NamedScope
(серьезно, найти их и прочитать и перечитать их)
DbContext
- единица работы, поэтому дальнейшая упаковка не требуется.
Поскольку вы хотите иметь несколько "запросов" в полете и хотите, чтобы между ними была единая Единица работы, вам необходимо:
Bind<DbContext>()
.ToMethod( ctx =>
new DbContext(
connectionStringName: ConfigurationUtility.GetConnectionString() ))
.InCallScope();
InCallScope()
означает, что:
- для данного графа объектов, составленного для одного
kernel.Get()
Вызов (следовательно, в области вызовов), каждый, для которого требуется DbContext
, получит тот же экземпляр.
-
IDisposable
. Dispose()
будет вызываться, когда для корневого объекта происходит событие Kernel.Release()
(или Kernel.Components.Get<ICache>().Clear()
для корня, если оно не .InCallScope()
)
Не должно быть оснований использовать InNamedScope()
и DefinesNamedScope()
; У вас нет долгоживущих объектов, которые вы пытаетесь исключить из пула/родительского/группирования по умолчанию.
Если вы сделаете это, вы сможете:
var command = kernel.Get<ICommand>();
try {
command.Execute();
} finally {
kernel.Components.Get<ICache>().Clear( command ); // Dispose of DbContext happens here
}
Реализация Command выглядит так:
class Command : ICommand {
readonly IAccountRepository _ar;
readonly IBlockedIpRepository _br;
readonly DbContext _ctx;
public Command(IAccountRepository ar, IBlockedIpRepository br, DbContext ctx){
_ar = ar;
_br = br;
_ctx = ctx;
}
void ICommand.Execute(){
_ar.Insert(a);
_br.Insert(b);
_ctx.saveChanges();
}
}
Обратите внимание, что в общем случае я избегаю наличия неявной единицы работы таким образом и вместо этого создаю ее создание и Disposal
. Это приводит к тому, что команда выглядит так:
class Command : ICommand {
readonly IAccountService _as;
readonly IBlockedIpService _bs;
readonly Func<DbContext> _createContext;
public Command(IAccountService @as, IBlockedIpServices bs, Func<DbContext> createContext){
_as = @as;
_bs = bs;
_createContext = createContext;
}
void ICommand.Execute(){
using(var ctx = _createContext()) {
_ar.InsertA(ctx);
_br.InsertB(ctx);
ctx.saveChanges();
}
}
Это не связано с использованием .InCallScope()
на Bind<DbContext>()
(но требует наличия Ninject.Extensions.Factory
FactoryModule
для синтеза Func<DbContext>
от простого Bind<DbContext>()
.
Ответ 2
Как обсуждалось в другом ответе, InCallScope
не является хорошим подходом к решению этой проблемы.
На данный момент я сбрасываю код, который работает с последними версиями NuGet Unstable/Include PreRelease/ Instal-Package -Pre
Ninject.Web.Common
без ясного объяснения. я переведет это в статью в вики Ninject.Extensions.NamedScope
на каком-то этапе, начали писать прохождение этой техники в Ninject.Extensions.NamedScope
wiki Статья CreateNamedScope/GetScope.
Возможно, некоторые бит также станут Pull Request (s) на каком-то этапе (подсказка Hat для @Remo Gloor, которая предоставила мне код схемы). связанные тесты и обучающие тесты находятся в этом контексте на данный момент), ожидая упаковки в надлежащем выпущенном формате TBD.
Сводка exec - это загрузить модуль ниже в ваше ядро и использовать .InRequestScope()
для всего, что вы хотите создать /Dispose
d для вызова обработчика, а затем передать запросы через IHandlerComposer.ComposeCallDispose
.
Если вы используете следующий модуль:
public class Module : NinjectModule
{
public override void Load()
{
Bind<IHandlerComposer>().To<NinjectRequestScopedHandlerComposer>();
// Wire it up so InRequestScope will work for Handler scopes
Bind<INinjectRequestHandlerScopeFactory>().To<NinjectRequestHandlerScopeFactory>();
NinjectRequestHandlerScopeFactory.NinjectHttpApplicationPlugin.RegisterIn( Kernel );
}
}
Какие провода в Factory [1] и NinjectHttpApplicationPlugin
, которые предоставляют:
public interface INinjectRequestHandlerScopeFactory
{
NamedScope CreateRequestHandlerScope();
}
Затем вы можете использовать этот Composer для запуска запроса InRequestScope()
:
public interface IHandlerComposer
{
void ComposeCallDispose( Type type, Action<object> callback );
}
Реализовано как:
class NinjectRequestScopedHandlerComposer : IHandlerComposer
{
readonly INinjectRequestHandlerScopeFactory _requestHandlerScopeFactory;
public NinjectRequestScopedHandlerComposer( INinjectRequestHandlerScopeFactory requestHandlerScopeFactory )
{
_requestHandlerScopeFactory = requestHandlerScopeFactory;
}
void IHandlerComposer.ComposeCallDispose( Type handlerType, Action<object> callback )
{
using ( var resolutionRoot = _requestHandlerScopeFactory.CreateRequestHandlerScope() )
foreach ( object handler in resolutionRoot.GetAll( handlerType ) )
callback( handler );
}
}
Материал инфраструктуры Ninject:
class NinjectRequestHandlerScopeFactory : INinjectRequestHandlerScopeFactory
{
internal const string ScopeName = "Handler";
readonly IKernel _kernel;
public NinjectRequestHandlerScopeFactory( IKernel kernel )
{
_kernel = kernel;
}
NamedScope INinjectRequestHandlerScopeFactory.CreateRequestHandlerScope()
{
return _kernel.CreateNamedScope( ScopeName );
}
/// <summary>
/// When plugged in as a Ninject Kernel Component via <c>RegisterIn(IKernel)</c>, makes the Named Scope generated during IHandlerFactory.RunAndDispose available for use via the Ninject.Web.Common <c>.InRequestScope()</c> Binding extension.
/// </summary>
public class NinjectHttpApplicationPlugin : NinjectComponent, INinjectHttpApplicationPlugin
{
readonly IKernel kernel;
public static void RegisterIn( IKernel kernel )
{
kernel.Components.Add<INinjectHttpApplicationPlugin, NinjectHttpApplicationPlugin>();
}
public NinjectHttpApplicationPlugin( IKernel kernel )
{
this.kernel = kernel;
}
object INinjectHttpApplicationPlugin.GetRequestScope( IContext context )
{
// TODO PR for TrgGetScope
try
{
return NamedScopeExtensionMethods.GetScope( context, ScopeName );
}
catch ( UnknownScopeException )
{
return null;
}
}
void INinjectHttpApplicationPlugin.Start()
{
}
void INinjectHttpApplicationPlugin.Stop()
{
}
}
}