8

Simple Injector IoC kabını kullanarak yapıcıya UnitOfWork enjekte edilen birden fazla hizmetim var.Basit Enjektör: Aynı grafikteki hizmetlerin aynı UnitOfWork örneğini enjekte edin

Şu anda her bir UnitOfWork örneğinin ayrı bir nesne olduğunu görüyorum. Bu, Entity Framework kullanıyorum ve tüm çalışma birimlerinde aynı içerik başvurusunu kullanmam nedeniyle kötü.

Her bir çözüm isteği başına aynı UnitOfWork örneğinin tüm hizmetlere enjekte edildiğinden nasıl emin olabilirim? Benim UnitOfWor benim komut tamamlandıktan sonra bir dış komut handler dekoratör tarafından kaydedilir.

Lütfen dikkat, bu ortak bir kütüphanedir ve hem MVC hem de Windows Forms için kullanılacaktır, mümkünse her iki platform için de genel bir çözüm bulunması hoş olacaktır.

Kod aşağıdadır: Burada

var handler = Resolve<ICommandHandler<SyncExternalDataCommand>>(); 

hizmetlerim şunlardır:

// snippet of code that registers types 
void RegisterTypes() 
{ 
    // register general unit of work class for use by majority of service layers 
    container.Register<IUnitOfWork, UnitOfWork>(); 

    // provide a factory for singleton classes to create their own units of work 
    // at will 
    container.RegisterSingle<IUnitOfWorkFactory, UnitOfWorkFactory>(); 

    // register logger 
    container.RegisterSingle<ILogger, NLogForUnitOfWork>(); 

    // register all generic command handlers 
    container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>), 
     AppDomain.CurrentDomain.GetAssemblies()); 

    container.RegisterDecorator(typeof(ICommandHandler<>), 
     typeof(TransactionCommandHandlerDecorator<>)); 

    // register services that will be used by command handlers 
    container.Register<ISynchronisationService, SynchronisationService>(); 
    container.Register<IPluginManagerService, PluginManagerService>(); 
} 

aşağıda hattının istenen sonucu inşa nesne grafiğinde boyunca paylaşılan UnitOfWork örneği olan bir nesne oluşturmak için :

public class PluginManagerService : IPluginSettingsService 
{ 
    public PluginManagerService(IUnitOfWork unitOfWork) 
    { 
     this.unitOfWork = unitOfWork; 
    } 

    private readonly unitOfWork; 

    void IPluginSettingsService.RegisterPlugins() 
    { 
     // manipulate the unit of work 
    } 
} 

public class SynchronisationService : ISynchronisationService 
{ 
    public PluginManagerService(IUnitOfWork unitOfWork) 
    { 
     this.unitOfWork = unitOfWork; 
    } 

    private readonly unitOfWork; 

    void ISynchronisationService.SyncData() 
    { 
     // manipulate the unit of work 
    } 
} 

public class SyncExternalDataCommandHandler 
    : ICommandHandler<SyncExternalDataCommand> 
{ 
    ILogger logger; 
    ISynchronisationService synchronisationService; 
    IPluginManagerService pluginManagerService; 

    public SyncExternalDataCommandHandler(
     ISynchronisationService synchronisationService, 
     IPluginManagerService pluginManagerService, 
     ILogger logger) 
    { 
     this.synchronisationService = synchronisationService; 
     this.pluginManagerService = pluginManagerService; 
     this.logger = logger; 
    } 

    public void Handle(SyncExternalDataCommand command) 
    { 
     // here i will call both services functions, however as of now each 
     // has a different UnitOfWork reference internally, we need them to 
     // be common. 
     this.synchronisationService.SyncData(); 
     this.pluginManagerService.RegisterPlugins(); 
    } 
} 

cevap

20

İhtiyaç duyduğunuz kayıt, başvuru türüne bağlıdır. iyon. İki farklı çerçeveden (MVC ve WinForms) bahsedeceğiniz için, her ikisinin de farklı bir kaydı olacaktır. Bir MVC uygulaması için (veya genel olarak web uygulamaları), yapılacak en yaygın şey, bir işyerini bir per web request basis'a kaydettirmektir. Örneğin, aşağıdaki kayıt tek bir web isteği sırasında işin birimi önbelleğe alır:

container.Register<IUnitOfWork>(() => 
{ 
    var items = HttpContext.Current.Items; 

    var uow = (IUnitOfWork)items["UnitOfWork"]; 

    if (uow == null) 
    { 
     items["UnitOfWork"] = uow = container.GetInstance<UnitOfWork>(); 
    } 

    return uow; 
}); 

bu kayıt olumsuz (gerekirse) iş birimi bertaraf olmamasıdır. Kapsayıcıya RegisterPerWebRequest uzantı yöntemlerini ekleyen Basit Enjektör için an extension package vardır; bu, örneğin web isteğinin sonunda otomatik olarak yerleştirilmesini sağlar. Tek bir (

container.Register<IUnitOfWork, UnitOfWork>(new WebRequestLifestyle()); 

Windows Öte yandan uygulama, genellikle Forms tek dişli:

container.RegisterPerWebRequest<IUnitOfWork, UnitOfWork>(); 

kısayol şudur: Bu paketi kullanarak aşağıdaki kayıt yapmak mümkün olacak kullanıcı bu uygulamayı kullanacaktır). Form başına tek bir iş birimine sahip olmanın alışılmadık bir durum olmadığına inanıyorum, ki bu da formu kapatacak, ancak komut/işleyici modelinin kullanımıyla, daha çok hizmet odaklı bir yaklaşım benimsemenin daha iyi olacağını düşünüyorum. Bununla demek istediğim, iş katmanını sunum katmanında değişiklik yapmaya gerek kalmadan bir WCF servisine taşıyabilecek şekilde tasarlamanın iyi olması. Komutlarınızın yalnızca ilkel maddeleri ve diğerlerini (0) DTO s içermesini sağlayarak bunu başarabilirsiniz. Bu yüzden Entity Framework varlıklarını komutlarınızda saklamayın, çünkü bu komutun daha seri hale getirilmesini sağlayacak ve daha sonra sürprizlere yol açacaktır.

Bunu yaptığınızda, komut işleyicisinin çalışmaya başlamadan önce yeni bir iş birimi oluşturmak, bu işleyicinin yürütülmesi sırasında aynı işi yeniden kullanmak ve işleyici başarılı bir şekilde tamamlandığında bunu gerçekleştirmek uygun olur (ve daima atınız). Bu, Per Lifetime Scope lifestyle için tipik bir senaryodur. Kapsayıcıya RegisterLifetimeScope uzantı yöntemlerini ekleyen an extension package var. Bu paketi kullanarak aşağıdaki kayıt yapmak mümkün olacak:, kayıt Ancak

container.Register<IUnitOfWork, UnitOfWork>(new LifetimeScopeLifestyle()); 

hikayenin sadece yarısıdır: bir kısayol olan

container.RegisterLifetimeScope<IUnitOfWork, UnitOfWork>(); 

. İkinci bölüm, iş birimindeki değişikliklerin ne zaman kaydedileceğine karar vermek ve bu kapsamı başlatmak ve bitirmek için Yaşam Boyu Kapsam yaşam tarzının kullanılması durumunda. Komut yürütmeden önce bir ömür boyu kapsamı başlatmanız ve komutun çalışması bittiğinde sona erdirmeniz gerektiğinden, bunu yapmanın en iyi yolu, komut işleyicilerinizi sarmalayabilen bir komut işleyici dekoratör kullanmaktır. Bu nedenle, Form Uygulaması için, genellikle ömür boyu kapsamı yöneten bir ekstra komut işleyici dekoratörü kaydedersiniz. Bu yaklaşım bu durumda çalışmaz. Aşağıdaki dekoratör bir göz atın, ama yanlış olduğunu unutmayın: dekore komut işleyicisi ömrü kapsamı başlatılır önce oluşturulur, çünkü bu yaklaşım , çalışmıyor

private class LifetimeScopeCommandHandlerDecorator<T> 
    : ICommandHandler<T> 
{ 
    private readonly Container container; 
    private readonly ICommandHandler<T> decoratedHandler; 

    public LifetimeScopeCommandHandlerDecorator(...) { ... } 

    public void Handle(T command) 
    { 
     using (this.container.BeginLifetimeScope()) 
     { 
      // WRONG!!! 
      this.decoratedHandler.Handle(command); 
     } 
    } 
} 

.

Biz şöyle bu sorunu çözmeye çalışırken içine cazip olabilir, ama bu doğru değildir ya:

using (this.container.BeginLifetimeScope()) 
{ 
    // EVEN MORE WRONG!!! 
    var handler = this.container.GetInstance<ICommandHandler<T>>(); 

    handler.Handle(command); 
} 

için bir ömür boyu kapsamı bağlamında içinde bir ICommandHandler<T> isteyerek, gerçekten bir IUnitOfWork enjekte rağmen Bu kapsamda, kap LifetimeScopeCommandHandlerDecorator<T> ile yeniden süslenmiş olan bir işleyiciyi iade edecektir. Bu nedenle, handler.Handle(command) numaralı telefonu arayarak, tekrarlayan bir çağrıyla sonuçlanacak ve bir yığın taşması istisnasıyla sonuçlanacağız.

Sorun, yaşam boyu kapsamını başlatamadan önce bağımlılık grafiğinin önceden oluşturulmuş olmasıdır. Dolayısıyla, grafiğin geri kalanı oluşturmayı erteleyerek bağımlılık grafiğini kırmamız gerekiyor. Uygulamanızın tasarımını temiz tutmanızı sağlayan en iyi yol], dekoratörün bir proxy'ye dönüştürülmesi ve sarılması gereken türü oluşturacak bir fabrikaya enjekte edilmesidir. Böyle LifetimeScopeCommandHandlerProxy<T> aşağıdaki gibi görünecektir:

// This class will be part of the Composition Root of 
// the Windows Forms application 
private class LifetimeScopeCommandHandlerProxy<T> : ICommandHandler<T> 
{ 
    // Since this type is part of the composition root, 
    // we are allowed to inject the container into it. 
    private Container container; 
    private Func<ICommandHandler<T>> factory; 

    public LifetimeScopeCommandHandlerProxy(Container container, 
     Func<ICommandHandler<T>> factory) 
    { 
     this.factory = factory; 
     this.container = container; 
    } 

    public void Handle(T command) 
    { 
     using (this.container.BeginLifetimeScope()) 
     { 
      var handler = this.factory(); 

      handler.Handle(command);   
     } 
    } 
} 

bir temsilci enjekte ederek, örneği oluşturulur süreyi uzatabilir ve bunu yaparak biz yapımını (geri kalanı) bağımlılık grafiği geciktirir. Bu hile şimdi, bu vekil sınıfını, kendini tekrar enjekte etmek yerine (tabii ki) sarılmış örnekleri enjekte edecek şekilde kaydetmektir. Basit Enjektör, Func<T> fabrikalarını dekoratörlere enjekte etmeyi destekler, böylece RegisterDecorator'u ve bu durumda RegisterSingleDecorator uzantı yöntemini bile kullanabilirsiniz.

Dekoratörlerin (ve bu proxy'nin) kaydedilme sırasının (açıkça) önemli olduğunu unutmayın. Bu proxy yeni bir yaşam boyu kapsamı başlattığından, iş birimini işleyen dekoratörü sarmalı.Başka bir deyişle, daha eksiksiz bir kayıt şu şekilde görünecektir:

container.RegisterLifetimeScope<IUnitOfWork, UnitOfWork>(); 

container.RegisterManyForOpenGeneric(
    typeof(ICommandHandler<>), 
    AppDomain.CurrentDomain.GetAssemblies()); 

// Register a decorator that handles saving the unit of 
// work after a handler has executed successfully. 
// This decorator will wrap all command handlers. 
container.RegisterDecorator(
    typeof(ICommandHandler<>), 
    typeof(TransactionCommandHandlerDecorator<>)); 

// Register the proxy that starts a lifetime scope. 
// This proxy will wrap the transaction decorators. 
container.RegisterSingleDecorator(
    typeof(ICommandHandler<>), 
    typeof(LifetimeScopeCommandHandlerProxy<>)); 

vekil kaydedilmesi ve TransactionCommandHandlerDecorator<T> bağımlılık grafiğinin kalanından farklı bir IUnitOfWork bağlı olacağı anlamına gelecektir tersi dekoratör, bağlantıda bulunduğu Bu grafikteki iş biriminde yapılan tüm değişikliklerin gerçekleşmeyeceği anlamına gelir. Başka bir deyişle, başvurunuz çalışmayı durduracaktır. Bu yüzden her zaman bu kaydı dikkatli bir şekilde gözden geçirin.

İyi şanslar.

İlgili konular