Hallo liebe Forum User,
ich versuche mich gerade an CQRS und verwende dazu MediatR als Hilfe. Nun habe ich aber das Problem das ich es nicht schaffe meinen DBContext in die Handler zu injecten.
Als ich Erfahrungen gesamelt habe mit dem Repository Pattern, habe ich es so gemacht:
var connection = @"Server=127.0.0.1;Port=5433;Database=PostgreSQL 11;UserId=postgres;Password=admin;";
services.AddTransient<IEmployeeRepository, EmployeeRepository>(parameter => new EmployeeRepository(connection));
(Ist nur eine lokale Datenbank, deswegen poste ich einfach mal den ganzen ConnectionString)
So funktioniert das leider nicht da MediatR zumal die ganze Struktur übernimmt.
Von einem User hier aus dem Forum habe ich noch aus meinem letzten Thread sein git Repository zugeschickt bekommen (ich weiß nicht ob ich hier Namen nennen darf), dort wurde es wie folgt gemacht:
services.AddSingleton<IDataContext>(_ => new InMemoryDataContext(
authorsCount: 25, booksCount: 200, booksPerAuthorMin: 3, booksPerAuthorMax: 12));
In meinem Fall habe ich keine Klasse und um ehrlich zu sein weiß ich nicht zu 100% wie genau das funktioniert. Ich wollte erst mit einer PostgreSQL Datenbank arbeiten, also quasi in den Handlern sofort query dahingehend ausführen etc. später würde ich allerdings auch mal versuchen Redis einzubauen um das cachen mal auszuprobieren.
Aber momentan schaffe ich es nicht Injection und MediatR in Kombination zu verwenden.
Ich galube, Du wirfst hier einiges durcheinander 😉
MediatR bietet erst einmal nur die Infrastruktur zum vermitteln (Mediator-Pattern) der Requests an die passenden Handler.
Das Injecten weiterer Abhängigkeiten der Handler hat nichts mit MediatR zu tun, sondern mit dem IoC-Container.
Du musst also folgendes tun:
Das Registrieren läuft dabei immer nach dem selben Prinzip ab:
services.AddEntityFrameworkSqlServer();
services.AddTransient<IMyRepository, MyRepository>();
// Weiteres für MediatR und die Handler
Nach diesem Prinzip registrierst Du alles, Du erstellst keine Abhängigkeit mehr selber, Du registrierst sie und der IoC-Container setzt alles zusammen. Oder Du verwendest die Klasse "ActivatorUtilities", die kann das auch.
Die Parameter musst Du nicht übergeben! Das übernimmt das Framework für dich, wenn es die Parameterwerte in den registrierten Services finden kann. Bei sowas wie z.B. String-Parametern musst Du dann konkreter arbeiten, aber ich gehe da lieber den Umweg über das FactoryPattern:
services.AddTransient<IMyServiceFactory, MyServiceFactory>();
services.AddTransient(c => c.GetService<IMyServiceFactory>().Create());
So könntest Du dann auch deine Datenbankverbindung unterbringen.
Das EntityFramework bietet dafür aber ein eigenes Konzept, es hat nämlich ein paar Erweiterungsmethoden, wie z.B. AddDbContext, worüber Du dann den DbContext konfigurieren kannst. Wenn Du eine Klasse registrierst, die den DbContext als Parameter braucht, wird der automatisch injected.
Ach ja:
Die Datenbankverbindung sollte nicht als Singleton registriert werden!
Ich würde die als Scoped registrieren, dann bleibt sie nur so lange offen, bis der Scope disposed wird.
Danke erstmal für die Hilfe, sehr freundlich 😃
ich habe mal versucht das umzusetzen, aber ich glaube das ist nicht richtig was ich mache (funktioniert nämlich nicht).
Ich verwende übrigends Dapper anstatt EF da ich das damit auch mal ausprobieren wollte.
Erstmal habe ich einfach eine ganz einfache Klasse erstellt + Interface damit ich das ganze hier ausprobieren kann.
public class DBContext : IDBContext
{
private readonly string _connectionString;
public DBContext()
{
_connectionString = "Server=127.0.0.1;Port=5433;Database=PostgreSQL 11;UserId=postgres;Password=admin;";
}
}
public interface IDBContext
{
}
Dann habe ich das in der Startup.cs so registriert:
services.AddScoped<IDBContext, DBContext>();
services.AddScoped(c => c.GetService<IDBContext>());
services.AddScoped<IRequestHandler<CreateUserQuery,UserViewModel>, Handler>();
Mein Handler sieht so aus:
using System.Threading;
using System.Threading.Tasks;
using MediatR;
public class Handler : IRequestHandler<CreateUserQuery,UserViewModel>
{
public readonly DBContext _dbcon;
public Handler (DBContext dbcon)
{
_dbcon = dbcon;
}
public async Task<UserViewModel> Handle(CreateUserQuery request, CancellationToken cancellationToken)
{
return new UserViewModel()
{
Id = 1,
FirstName = request.FirstName
};
}
}
IRequestHandler
IRequestHandler kommt von MediatR
Wenn ich das ganze so versuche bekomme ich nur die Fehlermeldung:
InvalidOperationException: Unable to resolve service for type 'DBContext' while attempting to activate 'Handler'.
InvalidOperationException: Error constructing handler for request of type MediatR.IRequestHandler`2[CreateUserQuery,UserViewModel]. Register your handlers with the container.
Ich dachte ich hätte mein Handler damit schon registriert aber anscheinend nicht.
Ich bin in der ganzen Thematik neu und lese viele Artikel und Dokus aber ich glaube ich bin total durcheinander geraten...
Da du IDBContext
registriert hast, mußt du dann auch diesen als Parameter angeben:
public readonly IDBContext _dbcon;
public Handler (IDBContext dbcon)
{
_dbcon = dbcon;
}
Das könnte (muss nicht) auch problematisch sein:
services.AddScoped<IDBContext, DBContext>();
services.AddScoped(c => c.GetService<IDBContext>());
Du registrierst hier IDBContext zwei Mal, beim zweiten Mal rufst Du die Instanz vom ersten Mal ab, also zwei Mal die selbe Instanz.
Ich meine stattdessen, dass Du eine IDBContextFactory schreibst:
public class DBContext : IDBContext
{
private readonly string _connectionString;
public DBContext(string connectionString)
{
_connectionString = connectionString;
}
}
public interface IDBContext
{
}
public interface IDBContextFactory
{
IDBContext CreateContext();
}
public interface DBContextFactory
{
public IDBContext CreateContext()
{
var connectionString = GetConnectionStringFromConfig();
return new DBContext(connectionString);
}
}
services.AddScoped<IDBContextFactory, DBContextFactory>();
services.AddScoped(c => c.GetServiceRequired<IDBContextFactory>().CreateContext());
Deine Variante reicht aber auch, dann lässt Du einfach die zweite, überflüssige Registrierung weg.
Das musst dann Du entscheiden, ob eine Factory für dich Sinn macht oder nicht, ich persönlich hab nur ungern die Verbindungsinformationen im DBContext bzw. hole sie dort aus z.B. der Config.
Ich würde keinen Factory Pattern hier verwenden; lohnt sich nicht - und die Pipe kann bei einer Factory eine Verbindung nicht automatisch disposen.
Darüber hinaus würde ich den Kontext in diesem Fall nicht als Scoped, sondern als Transient verwenden.
Scoped = ein Context pro Request(thread).
Sprich mehrere Handler würden sich einen Kontext teilen - bin ich jetzt nicht so nen riesen Fan von.
Ich würde jedem Handler nen eigenen Kontext verpassen (=> Transient)
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Danke erstmal für die Antworten. Sehr Hilfreiche Informationen.
Hier die vorgenommen Änderungen zur Lösung Falls jemand das selbe Problem hat:
Th69 hatte recht, ich hatte mich vertan und nicht das Interface genommen weil ich dachte die Klasse müsste verwendet werden.
Und Palladin007 hat ebenso recht das diese Zeile nicht gestimmt hat:
services.AddScoped(c => c.GetService<IDBContext>());
Diese Zeile hat den Fehler verursacht, aber ebenso war diese auch wie es aussieht nicht nötig:
services.AddScoped<IRequestHandler<CreateUserQuery,UserViewModel>, Handler>();
Den ohne diese Zeile funktioniert alles wie es soll.
Und danke Abt an die lehrreichen Informationen!
Es ist nicht notwendig jeden Handler zu registrieren.
Das passiert automatisch, wenn Du AddMediatR verwendest.
Dabei musst Du nur die Assembly angeben, in der sich die Handler befinden (siehe mein Sample, daher die Zeile Assembly engineAssembly = typeof(CommonEngine).Assembly;
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Ich würde keinen Factory Pattern hier verwenden; lohnt sich nicht - und die Pipe kann bei einer Factory eine Verbindung nicht automatisch disposen.
Das stimmt so nicht ganz.
Solange man es so registriert, wie ich das gezeigt habe, kann es disposed werden.
Getestet mit Microsoft.Extensions.DependencyInjection und Autofac; mit Scope und ohne.
Bei Autofac kann man das Verhalten auch abstellen, bei der Variante von Microsoft hab ich keine passende Option gefunden.
Dennoch sollte man sich natürlich überlegen, ob sich eine Factory lohnt oder nicht.
Pauschal verneinen würde ich es aber nicht.