8

Mam wiele usług, z których każda ma UnitOfWork wstrzyknięta do konstruktora przy użyciu kontenera IoC Simple Injector.Prosty iniektor: Inject to samo wystąpienie UnitOfWork dla usług tego samego wykresu.

Obecnie widzę, że każda instancja UnitOfWork jest osobnym obiektem, jest to złe, ponieważ używam Entity Framework i wymagam tego samego odniesienia kontekstowego we wszystkich jednostkach pracy.

Jak mogę zapewnić, że ta sama instancja UnitOfWork zostanie wstrzyknięta do wszystkich usług dla każdego żądania rozstrzygnięcia? Mój UnitOfWor zostanie zapisany przez zewnętrzny dekorator obsługi komend po wykonaniu polecenia.

Należy pamiętać, że jest to zwykła biblioteka i będzie używana zarówno w MVC, jak i w Windows Forms, dobrze byłoby mieć ogólne rozwiązanie dla obu platform, jeśli to możliwe.

Kod jest poniżej:

// 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>(); 
} 

pożądany wynik poniżej linii jest stworzenie obiektu, który ma wspólną instancji UnitOfWork całym skonstruowanego wykresu obiektu:

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

Oto moje usługi :

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(); 
    } 
} 

Odpowiedz

20

Wymagana rejestracja zależy od rodzaju aplikacji jon. Ponieważ mówimy o dwóch różnych frameworkach (MVC i WinForm), obie będą miały inną rejestrację.

W przypadku aplikacji MVC (lub ogólnie aplikacji internetowych) najczęstszą rzeczą do zrobienia jest zarejestrowanie jednostki pracy na per web request basis. Na przykład, po rejestracji będzie buforować jednostkę pracy podczas pojedynczego żądania internetowej:

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

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

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

    return uow; 
}); 

Wadą tej rejestracji jest to, że jednostka pracy nie jest umieszczona (w razie potrzeby). Istnieje an extension package dla prostego wtryskiwacza, który dodaje do kontenera metody rozszerzenia RegisterPerWebRequest, które automatycznie zapewnią, że instancja zostanie usunięta na końcu żądania WWW. Korzystanie z tego pakietu, będzie można wykonać następujące czynności rejestracji:

container.RegisterPerWebRequest<IUnitOfWork, UnitOfWork>(); 

który jest skrótem do:

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

Windows Forms aplikacji z drugiej strony, jest zazwyczaj pojedynczy gwintowany (pojedynczy użytkownik będzie korzystać z tej aplikacji). Uważam, że nie jest niczym niezwykłym posiadanie pojedynczej jednostki pracy na formę, która jest umieszczona w formularzu zamyka, ale przy użyciu wzorca polecenia/obsługi, myślę, że lepiej jest przyjąć podejście bardziej zorientowane na usługi. Rozumiem przez to, że dobrze byłoby zaprojektować go w taki sposób, aby można było przenieść warstwę biznesową do usługi WCF, bez konieczności wprowadzania zmian w warstwie prezentacji. Możesz to osiągnąć, pozwalając swoim komendom tylko zawierać prymitywy i (inne) DTO s. Więc nie przechowuj encji Entity Framework w swoich komendach, ponieważ znacznie utrudni to serializowanie polecenia, a później doprowadzi do niespodzianek.

Gdy to zrobisz, wygodniej będzie utworzyć nową jednostkę pracy przed uruchomieniem komendy obsługi, ponownie użyć tej samej jednostki pracy podczas wykonywania tego modułu obsługi i zatwierdzić ją, gdy procedura obsługi zakończy się pomyślnie (i zawsze wyrzucaj go). Jest to typowy scenariusz dla Per Lifetime Scope lifestyle. Jest an extension package, który dodaje metody rozszerzenia RegisterLifetimeScope do kontenera. Korzystanie z tego pakietu, będzie można zrobić po rejestracji:

container.RegisterLifetimeScope<IUnitOfWork, UnitOfWork>(); 

który jest skrót do:

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

Rejestracja jest jednak tylko połowa historii. Druga część to decyzja, kiedy zapisać zmiany w jednostce pracy, oraz w przypadku korzystania ze stylu życia w ramach Lifetime Scope, od czego zacząć i zakończyć taki zakres. Ponieważ powinieneś jawnie uruchomić zasięg życia przed wykonaniem polecenia i zakończyć go, gdy polecenie zakończyło wykonywanie, najlepszym sposobem na to jest użycie dekoratora obsługi komend, który może owijać programy obsługi komend. Dlatego w przypadku aplikacji formularzy zwykle rejestruje się dodatkowy dekorator obsługi poleceń, który zarządza zasięgiem życia. Takie podejście nie działa w tym przypadku. Spójrz na poniższy dekoratora, ale należy pamiętać, że jest to błędne :

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); 
     } 
    } 
} 

Podejście nie działa, ponieważ urządzone obsługi poleceń jest tworzony przed uruchomieniu zakres życia.

Możemy być kuszeni do starają się rozwiązać ten problem w następujący sposób, ale to nie jest poprawna:

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

    handler.Handle(command); 
} 

Chociaż żądając ICommandHandler<T> wewnątrz kontekście zakresu twórczości, czy rzeczywiście do wstrzykiwania IUnitOfWork w tym zakresie, kontener zwróci przewodnik, który jest (ponownie) ozdobiony numerem LifetimeScopeCommandHandlerDecorator<T>. Wywołanie handler.Handle(command) spowoduje wywołanie rekursywne, a otrzymamy wyjątek przepełnienia stosu.

Problem polega na tym, że wykres zależności został już zbudowany, zanim będziemy mogli uruchomić zakres życia. Musimy zatem złamać wykres zależności, odraczając budowanie reszty wykresu. Najlepszym sposobem, aby to zrobić, aby zachować czysty design aplikacji] jest zmiana dekoratora na proxy i wstrzyknięcie do niego fabryki, która stworzy typ, który miał zawinąć. Takie LifetimeScopeCommandHandlerProxy<T> będzie wyglądać następująco:

// 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);   
     } 
    } 
} 

Wtryskując pełnomocnika, można opóźnić wystąpienie i powstaje w ten sposób, że opóźnienie w konstrukcję (reszta) wykres zależności. Sztuczka polega teraz na zarejestrowaniu tej klasy proxy w taki sposób, aby wstrzyknęła zawinięte instancje, zamiast (oczywiście) wstrzyknąć sobie ponownie. Simple Injector obsługuje wstrzykiwanie Func<T> fabryk do dekoratorów, więc możesz po prostu użyć RegisterDecorator, aw tym przypadku nawet metody rozszerzania RegisterSingleDecorator.

Należy zauważyć, że kolejność, w jakiej dekoratorzy (i ten pełnomocnik) są zarejestrowani (oczywiście) ma znaczenie. Ponieważ ten serwer proxy uruchamia nowy zakres życia, powinien zawinąć dekorator, który zatwierdza jednostkę pracy.Innymi słowy, bardziej kompletna rejestracja będzie wyglądać następująco:

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<>)); 

Rejestrowanie serwera proxy i dekorator na odwrót oznaczałoby, że TransactionCommandHandlerDecorator<T> będzie zależeć od innego IUnitOfWork od reszty wykresu zależności robi, co oznaczałoby, że wszystkie zmiany wprowadzone do jednostki pracy na tym wykresie nie zostaną zatwierdzone. Innymi słowy, twoja aplikacja przestanie działać. Dlatego zawsze dokładnie sprawdzaj tę rejestrację.

Powodzenia.

Powiązane problemy