8

Ich habe mehrere Dienste, von denen jeder eine UnitOfWork in den Konstruktor mit dem Simple Injector IoC Container injiziert.Simple Injector: Inject selbe UnitOfWork-Instanz über Dienste des gleichen Graphen

Derzeit kann ich jede UnitOfWork Instanz ein separates Objekt sehen, das ist schlecht, da ich Entity Framework verwende und die gleiche Kontextreferenz über alle Einheiten der Arbeit benötigen.

Wie kann ich sicherstellen, dass die gleiche UnitOfWork Instanz in jede Dienste für jede Auflösungsanforderung injiziert wird? Mein UnitOfWor wird nach Abschluss des Befehls von einem externen Command-Handler-Decorator gespeichert.

Bitte beachten Sie, dies ist eine gemeinsame Bibliothek und wird für MVC und Windows Forms verwendet werden, wäre es eine gute Idee, eine generische Lösung für beide Plattformen wenn möglich zu haben.

-Code ist unten:

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

Das gewünschte Ergebnis der unterhalb der Linie ist es, ein Objekt zu erstellen, die eine gemeinsame UnitOfWork Instanz im ganzen konstruierten Objektgraph hat:

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

Hier meine Dienste sind :

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

Antwort

20

Welche Registrierung Sie benötigen, hängt von der Art der Anwendung ab Ion. Da Sie über zwei verschiedene Frameworks sprechen (MVC und WinForms), haben beide eine unterschiedliche Registrierung.

Für eine MVC-Anwendung (oder Webanwendungen im Allgemeinen) ist es am häufigsten, die Arbeitseinheit auf einem per web request basis zu registrieren. Zum Beispiel wird die folgende Anmeldung die Arbeitseinheit während eines einzelnen Web-Anfrage-Cache:

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

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

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

    return uow; 
}); 

Der Nachteil dieser Anmeldung ist, dass die Arbeitseinheit nicht angeordnet ist (falls erforderlich). Es gibt an extension package für den Simple Injector, der dem Container RegisterPerWebRequest Erweiterungsmethoden hinzufügt, wodurch automatisch sichergestellt wird, dass die Instanz am Ende der Webanforderung bereitgestellt wird. Mit diesem Paket können Sie die folgende Registrierung tun:

container.RegisterPerWebRequest<IUnitOfWork, UnitOfWork>(); 

, die eine Abkürzung ist:

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

Eine Windows Forms-Anwendung auf der anderen Seite ist in der Regel Single-Threaded (ein einzelnes Benutzer wird diese Anwendung verwenden). Ich glaube, dass es nicht ungewöhnlich ist, eine einzige Arbeitseinheit pro Formular zu haben, die in der Form geschlossen ist, aber mit dem Befehl/Handler-Muster denke ich, dass es besser ist, einen serviceorientierten Ansatz zu wählen. Was ich damit meine ist, dass es gut wäre, es so zu gestalten, dass Sie die Business-Schicht in einen WCF-Dienst verschieben können, ohne Änderungen an der Präsentationsebene vornehmen zu müssen. Sie können dies erreichen, indem Sie Ihre Befehle nur Primitive und (andere) DTO s enthalten lassen. Speichern Sie daher Entity Framework-Entitäten nicht in Ihren Befehlen, da dies die Serialisierung des Befehls erheblich erschwert und später zu Überraschungen führt.

Wenn Sie dies tun, wäre es bequem, eine neue Arbeitseinheit zu erstellen, bevor der Command-Handler die Ausführung startet, dieselbe Arbeitseinheit während der Ausführung dieses Handlers wieder verwendet und sie festschreibt, wenn der Handler erfolgreich abgeschlossen wurde (und immer entsorgen). Dies ist ein typisches Szenario für die Per Lifetime Scope lifestyle. Es gibt an extension package, die dem Container RegisterLifetimeScope Erweiterungs-Methoden hinzufügt. Mit diesem Paket können Sie die folgende Registrierung tun:

container.RegisterLifetimeScope<IUnitOfWork, UnitOfWork>(); 

, die eine Abkürzung ist:

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

Die Registrierung ist jedoch nur die Hälfte der Geschichte. Der zweite Teil besteht darin, zu entscheiden, wann die Änderungen der Arbeitseinheit gespeichert werden sollen, und im Falle der Verwendung des Lifetime Scope-Lebensstils, wo ein solcher Bereich gestartet und beendet wird. Da Sie vor der Ausführung des Befehls explizit einen Gültigkeitszeitraum für die Gültigkeitsdauer starten sollten und ihn beenden sollen, wenn der Befehl ausgeführt wurde, verwenden Sie am besten einen Befehlshandler-Decorator, der die Befehlsroutinen umbrechen kann. Daher würden Sie normalerweise für die Formularanwendung einen zusätzlichen Befehlshandler-Decorator registrieren, der den Lebensdauerumfang verwaltet. Dieser Ansatz funktioniert in diesem Fall nicht. Werfen Sie einen Blick auf die folgenden Dekorateur, aber bitte beachten Sie, dass es falsch ist:

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

Dieser Ansatz nicht nicht funktioniert, weil die eingerichtete Befehlshandler erstellt wird vor die Lebensdauer Umfang gestartet wird.

Wir könnten, zu versuchen, versucht sein, dieses Problem zu lösen, wie folgt, aber das ist nicht richtig, entweder:

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

    handler.Handle(command); 
} 

Obwohl eine ICommandHandler<T> innerhalb Kontext eines ganzen Lebens Rahmen anfordern, in der Tat ein nicht injizieren IUnitOfWork für In diesem Bereich gibt der Container einen Handler zurück, der (wieder) mit einem LifetimeScopeCommandHandlerDecorator<T> dekoriert ist. Das Aufrufen von handler.Handle(command) wird daher zu einem rekursiven Aufruf führen, und wir werden mit einer Stapelüberlaufausnahme enden.

Das Problem besteht darin, dass das Abhängigkeitsdiagramm bereits erstellt wurde, bevor wir den Lebensdauerbereich starten können. Wir müssen daher den Abhängigkeitsgraphen aufbrechen, indem wir den Rest des Graphen verzögern. Der beste Weg, dies zu tun, damit Sie Ihr Anwendungsdesign sauber halten können, besteht darin, den Decorator in einen Proxy zu verwandeln und eine Factory in ihn einzufügen, die den Typ erzeugt, den er umbrechen sollte. Solche LifetimeScopeCommandHandlerProxy<T> wird wie folgt aussehen:

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

durch einen Delegierten Injektion, können wir die Zeit verzögern die Instanz erstellt wird und wir durch diese Weise den Aufbau (der Rest), um die Abhängigkeitsgraphen verzögern. Der Trick besteht nun darin, diese Proxy-Klasse so zu registrieren, dass sie die umhüllten Instanzen einschleusen wird, anstatt sich (natürlich) erneut zu injizieren. Simple Injector unterstützt das Einfügen von Func<T> Fabriken in Dekoratoren, so dass Sie einfach die RegisterDecorator und in diesem Fall sogar die RegisterSingleDecorator Erweiterungsmethode verwenden können.

Beachten Sie, dass die Reihenfolge, in der Decorators (und dieser Proxy) registriert sind (offensichtlich) wichtig ist. Da dieser Proxy einen neuen lebenslangen Gültigkeitsbereich startet, sollte er den Decorator umbrechen, der die Arbeitseinheit festlegt.Mit anderen Worten würde eine vollständigere Registrierung wie folgt aussehen:

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

Registrieren des Proxy und Dekorateur umgekehrt bedeuten würde, dass die TransactionCommandHandlerDecorator<T> auf einem anderen IUnitOfWork als der Rest des Abhängigkeitsgraphen abhängen würde der Fall ist, die würde bedeuten, dass alle Änderungen an der Arbeitseinheit in diesem Diagramm nicht übernommen werden. Mit anderen Worten, Ihre Anwendung wird nicht mehr funktionieren. Überprüfen Sie diese Registrierung daher immer sorgfältig.

Viel Glück.