Поиск области 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()
        {
        }
    }
}