2016-07-27 29 views
1

Ich experimentierte mit dem Mediatormuster und CQRS mit der Bibliothek MediatR in einer WinForms-Anwendung, die das Entity Framework für den Datenzugriff verwendet. Die Anwendung wird in einer Batch-Produktionsanlage verwendet und ermöglicht es Benutzern, eine Liste der aktiven und abgeschlossenen Chargen zu sehen und bei Bedarf Aktualisierungen der Chargeninformationen vorzunehmen. Zu jeder Charge gehört eine große Menge an Informationen, z. B. Qualitäts- und Prozessmessungen. Lesen und Schreiben von Daten organisiert in Abfragen und Befehle, basierend auf diesen Artikel:Probleme mit Abhängigkeitsbereichen mit MediAtR und SimpleInjector

Meanwhile... on the query side of my architecture

CQRS with MediatR and AutoMapper

Hier ist ein einfaches Beispiel für eine Abfrage und Abfrage-Handler. DataContext wird mit SimpleInjector in den Abfrage-Handler injiziert.

public class GetAllBatchesQuery: IRequest<IEnumerable<Batch>> { } 

public class GetAllBatchesQueryHandler : 
    IRequestHandler<GetAllBatchesQuery, IEnumerable<Batch>> 
{ 
    private readonly DataContext _context; 

    public GetAllBatchesQueryHandler(DataContext context) 
    { 
     _context= context; 
    } 

    public IEnumerable<Batch> Handle(GetAllBatchesQueryrequest) 
    { 
     return _db.Batches.ToList(); 
    } 
} 

Dies würde von den Vortragenden wie folgt aufgerufen werden:

var batches = mediator.Send(new GetAllBatchesQuery()); 

Das Problem, das ich in renne mit der Lebensdauer des DbContext ist. Im Idealfall würde Ich mag eine einzelne Instanz pro isoliert Transaktion verwenden, wäre das in diesem Fall gehören solche Dinge wie:

  • Abrufen der Liste der Stapel aus der Datenbank
  • für eine Liste von Qualitätsmetriken Abrufen Batch (diese in einer anderen Datenbank und den Zugriff über eine gespeicherte Prozedur gespeichert werden)
  • ein Batch-Aktualisierung, die mich

in der Datenbank mehr Entitäten Aktualisierung enthalten kann zu einem scoped oder transientem Lebensstil für DbContext Dies würde dazu führen, . Wenn jedoch eine vorübergehende Lebensstil verwendet wird, wirft SimpleInjector den folgenden Fehler, die ausgelöst wird, wenn die Art der Registrierung wie folgt:

container.Register<DataContext>(); 

Eine nicht behandelte Ausnahme des Typs ‚SimpleInjector.DiagnosticVerificationException‘ aufgetreten in SimpleInjector.dll

Weitere Informationen: Die Konfiguration ist ungültig. Die folgenden Diagnosewarnungen wurden gemeldet:

- [Einweg-Übergangskomponente] DataContext wird als vorübergehend registriert, implementiert jedoch IDisposable.

Erforschung dieses Themas auf der SimpleInjector Website ergibt folgende note:

Warnung: Transient-Instanzen werden nicht durch den Behälter verfolgt. Dies bedeutet, dass der Simple Injector keine transienten Instanzen anordnet.

Dies führte mich den Weg der Verwendung eines Lifetime Scope Lifestyle für den DataContext.Um dies zu erreichen, habe ich eine neue Dekorateur-Klasse für meine Fragen und registriert es wie folgt:

public class LifetimeScopeDecorator<TRequest, TResponse> : 
    IRequestHandler<TRequest, TResponse> 
    where TRequest : IRequest<TResponse> 
{ 
    private readonly IRequestHandler<TRequest, TResponse> _decorated; 
    private readonly Container _container; 

    public LifetimeScopeDecorator(
     IRequestHandler<TRequest, TResponse> decorated, 
     Container container) 
    { 
     _decorated = decorated; 
     _container = container; 
    } 

    public TResponse Handle(TRequest message) 
    { 
     using (_container.BeginLifetimeScope()) 
     { 
      var result = _decorated.Handle(message); 
      return result; 
     } 
    } 
} 

... 

container.RegisterDecorator(
    typeof(IRequestHandler<,>), 
    typeof(ExecutionContextScopeDecorator<,>)); 

jedoch, dass der Wandel zu machen verursacht eine andere Ausnahme, diesmal in der folgenden Zeile geworfen:

var batches = mediator.Send(new GetAllBatchesQuery()); 

Eine nicht behandelte Ausnahme des Typs ‚System.InvalidOperationException‘ aufgetreten in MediatR.dll

Zusätzliche Informationen: Handler wurde nicht für die Anforderung des Typ MediatorTest.GetAllBatchesQuery gefunden.

Container- oder Service-Locator nicht richtig konfiguriert oder Handler nicht mit Ihrem Container registriert.

Nach Debuggen und auf der Suche durch den MediatR Code, scheint es, dass, wenn die mediator.Send(...) Methode aufgerufen wird, wird eine neue Instanz der Klasse GetAllBatchesQueryHandler durch den Aufruf container.GetInstance() erstellt wird. Da sich DataContext zu diesem Zeitpunkt jedoch nicht in einem Ausführungsbereich befindet, kann es möglicherweise nicht ordnungsgemäß initialisiert werden, wodurch die Ausnahme verursacht wird.

Ich glaube, ich verstehe die Ursache des Problems, aber ich weiß nicht, wie man es effektiv lösen kann. Um dieses Problem besser zu veranschaulichen, habe ich das folgende Minimalbeispiel entwickelt. Jede Klasse, die IDisposable implementiert, führt zu dem gleichen Problem, das ich mit DataContext habe.

using System; 
using System.Collections.Generic; 
using System.Reflection; 
using MediatR; 
using SimpleInjector; 
using SimpleInjector.Extensions.LifetimeScoping; 

namespace MediatorTest 
{ 
    public class GetRandomQuery : IRequest<int> 
    { 
     public int Min { get; set; } 
     public int Max { get; set; } 
    } 

    public class GetRandomQueryHandler : IRequestHandler<GetRandomQuery, int> 
    { 
     private readonly RandomNumberGenerator _r; 

     public GetRandomQueryHandler(RandomNumberGenerator r) 
     { 
      _r = r; 
     } 

     public int Handle(GetRandomQuery request) 
     { 
      return _r.Next(request.Min, request.Max); 
     } 
    } 

    public class RandomNumberGenerator : IDisposable 
    { 
     private Random _random = new Random(); 

     public RandomNumberGenerator() { } 

     public void Dispose() { } 

     public int Next(int min, int max) 
     { 
      var result = _random.Next(min, max); 
      return result; 
     } 
    } 

    public class LifetimeScopeDecorator<TRequest, TResponse> : 
     IRequestHandler<TRequest, TResponse> 
     where TRequest : IRequest<TResponse> 
    { 
     private readonly IRequestHandler<TRequest, TResponse> _decorated; 
     private readonly Container _container; 

     public LifetimeScopeDecorator(
      IRequestHandler<TRequest, TResponse> decorated, 
      Container container) 
     { 
      _decorated = decorated; 
      _container = container; 
     } 

     public TResponse Handle(TRequest message) 
     { 
      using (_container.BeginLifetimeScope()) 
      { 
       var result = _decorated.Handle(message); 
       return result; 
      } 
     } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      var assemblies = GetAssemblies(); 

      var container = new Container(); 
      container.Options.DefaultScopedLifestyle = new LifetimeScopeLifestyle(); 
      container.RegisterSingleton<IMediator, Mediator>(); 
      container.Register<RandomNumberGenerator>(Lifestyle.Scoped); 
      container.Register(typeof(IRequestHandler<,>), assemblies); 
      container.RegisterSingleton(new SingleInstanceFactory(container.GetInstance)); 
      container.RegisterSingleton(new MultiInstanceFactory(container.GetAllInstances)); 
      container.RegisterDecorator(
       typeof(IRequestHandler<,>), 
       typeof(LifetimeScopeDecorator<,>)); 

      container.Verify(); 

      var mediator = container.GetInstance<IMediator>(); 

      var value = mediator.Send(new GetRandomQuery() { Min = 1, Max = 100 }); 

      Console.WriteLine("Value = " + value); 

      Console.ReadKey(); 
     } 

     private static IEnumerable<Assembly> GetAssemblies() 
     { 
      yield return typeof(IMediator).GetTypeInfo().Assembly; 
      yield return typeof(GetRandomQuery).GetTypeInfo().Assembly; 
     } 
    } 
} 

Antwort

2

Das Problem ist, dass Ihr decoratee (mit seiner DbContext Abhängigkeit) zu der Zeit erzeugt wird, der Dekorateur erstellt wird, und zu diesem Zeitpunkt gibt es keinen aktiven Bereich (da Sie es zu einem späteren Zeitpunkt erstellen) . Sie sollten eine Fabrik wie beschrieben here verwenden. Mit anderen Worten, sollte Ihre LifetimeScopeDecorator wie folgt umgesetzt werden:

public class LifetimeScopeDecorator<TRequest, TResponse> : 
    IRequestHandler<TRequest, TResponse> 
    where TRequest : IRequest<TResponse> 
{ 
    private readonly Func<IRequestHandler<TRequest, TResponse>> _decorateeFactory; 
    private readonly Container _container; 

    public LifetimeScopeDecorator(
     Func<IRequestHandler<TRequest, TResponse>> decorateeFactory, 
     Container container) 
    { 
     _decorateeFactory = decorateeFactory; 
     _container = container; 
    } 

    public TResponse Handle(TRequest message) 
    { 
     using (_container.BeginLifetimeScope()) 
     { 
      var result = _decorateeFactory.Invoke().Handle(message); 
      return result; 
     } 
    } 
} 

Der Unterschied mit Ihrer ursprünglichen Implementierung ist, dass ein Func<IRequestHandler<TRequest, TResponse>> anstelle ein IRequestHandler<TRequest, TResponse> injiziert wird. Dies ermöglicht Simple Injector, die Erstellung nach dem Erstellen des Bereichs zu verschieben.

+1

Funktioniert perfekt! Vielen Dank! –