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;
}
}
}
Funktioniert perfekt! Vielen Dank! –