Während eines Vorstellungsgesprächs wurde ich gebeten, zu erklären, warum das Repository-Muster kein gutes Muster für die Arbeit mit ORMs wie Entity Framework ist. Warum ist das so?
Während eines Vorstellungsgesprächs wurde ich gebeten, zu erklären, warum das Repository-Muster kein gutes Muster für die Arbeit mit ORMs wie Entity Framework ist. Warum ist das so?
Antworten:
Ich sehe keinen Grund dafür, dass das Repository-Muster nicht mit Entity Framework funktioniert. Das Repository-Muster ist eine Abstraktionsebene, die Sie in Ihre Datenzugriffsebene einfügen. Ihre Datenzugriffsebene kann aus reinen gespeicherten ADO.NET-Prozeduren, Entity Framework oder einer XML-Datei bestehen.
In großen Systemen, in denen Daten aus verschiedenen Quellen (Datenbank / XML / Webdienst) stammen, ist eine Abstraktionsschicht sinnvoll. Das Repository-Muster funktioniert in diesem Szenario gut. Ich glaube nicht, dass Entity Framework genug Abstraktion ist, um zu verbergen, was sich hinter den Kulissen abspielt.
Ich habe das Repository-Muster mit Entity Framework als meine Datenzugriffsschichtmethode verwendet und stehe noch vor einem Problem.
Ein weiterer Vorteil der Zusammenfassung DbContext
mit einem Repository ist die Testbarkeit der Einheiten . Sie können über eine IRepository
Schnittstelle verfügen , für die zwei Implementierungen vorhanden sind, eine (das echte Repository) DbContext
, mit der mit der Datenbank gesprochen wird, und die zweite, FakeRepository
die speicherinterne Objekte / verspottete Daten zurückgeben kann. Dies macht Ihre IRepository
Unit testbar, also andere Teile des Codes, die verwendet werden IRepository
.
public interface IRepository
{
IEnumerable<CustomerDto> GetCustomers();
}
public EFRepository : IRepository
{
private YourDbContext db;
private EFRepository()
{
db = new YourDbContext();
}
public IEnumerable<CustomerDto> GetCustomers()
{
return db.Customers.Select(f=>new CustomerDto { Id=f.Id, Name =f.Name}).ToList();
}
}
public MockRepository : IRepository
{
public IEnumerable<CustomerDto> GetCustomers()
{
// to do : return a mock list of Customers
// Or you may even use a mocking framework like Moq
}
}
Mit DI erhalten Sie jetzt die Implementierung
public class SomeService
{
IRepository repo;
public SomeService(IRepository repo)
{
this.repo = repo;
}
public void SomeMethod()
{
//use this.repo as needed
}
}
Der beste Grund, das Repository-Muster nicht mit Entity Framework zu verwenden? Entity Framework implementiert bereits ein Repository-Muster. DbContext
ist Ihre UOW (Unit of Work) und jede DbSet
ist das Repository. Darüber hinaus ist die Implementierung einer weiteren Schicht nicht nur redundant, sondern erschwert auch die Wartung.
Menschen folgen Mustern, ohne den Zweck des Musters zu erkennen. Im Fall des Repository-Musters besteht der Zweck darin, die Datenbankabfragelogik auf niedriger Ebene zu abstrahieren. In früheren Zeiten, als Sie SQL-Anweisungen in Ihren Code geschrieben haben, war das Repository-Muster eine Möglichkeit, SQL aus einzelnen Methoden, die über Ihre Codebasis verstreut waren, zu entfernen und an einem Ort zu lokalisieren. Ein ORM wie Entity Framework, NHibernate usw. ist ein Ersatz für diese Codeabstraktion und macht das Muster als solches überflüssig .
Es ist jedoch keine schlechte Idee, eine Abstraktion auf Ihrem ORM zu erstellen, nur nicht alles, was so komplex ist wie UoW / Repostitory. Ich würde mit einem Dienstmuster arbeiten, bei dem Sie eine API erstellen, die Ihre Anwendung verwenden kann, ohne zu wissen oder sich darum zu kümmern, ob die Daten von Entity Framework, NHibernate oder einer Web-API stammen. Dies ist viel einfacher, da Sie Ihrer Serviceklasse lediglich Methoden hinzufügen, um die Daten zurückzugeben, die Ihre Anwendung benötigt. Wenn Sie zum Beispiel eine Aufgaben-App geschrieben haben, können Sie einen Serviceabruf erhalten, um Artikel zurückzugeben, die diese Woche fällig sind und noch nicht fertiggestellt wurden. Ihre App weiß nur, dass sie diese Methode aufruft, wenn sie diese Informationen benötigt. Innerhalb dieser Methode und in Ihrem Service im Allgemeinen interagieren Sie mit Entity Framework oder was auch immer Sie sonst verwenden. Wenn Sie später entscheiden, ORMs zu wechseln oder die Informationen von einer Web-API abzurufen,
Es mag so klingen, als wäre dies ein mögliches Argument für die Verwendung des Repository-Musters, aber der Hauptunterschied besteht darin, dass ein Service eine dünnere Schicht ist und darauf abzielt, vollständig gebackene Daten zurückzugeben, anstatt auf etwas, das Sie weiterhin abfragen, wie z. B. mit einem Repository.
DbContext
in EF6 + (siehe: msdn.microsoft.com/en-us/data/dn314429.aspx ). Auch in kleineren Versionen können Sie eine gefälschte verwenden DbContext
-artige Klasse mit verspottete DbSet
s, da DbSet
Geräte ein iterface, IDbSet
.
Hier ist eine Aufnahme von Ayende Rahien: Architektur in der Grube des Verhängnisses: Die Übel der Abstraktionsschicht des Endlagers
Ich bin mir noch nicht sicher, ob ich mit seiner Schlussfolgerung einverstanden bin. Es ist ein Haken: Einerseits kann ich, wenn ich meinen EF-Kontext in typspezifische Repositorys mit abfragespezifischen Datenabrufmethoden einhülle, meinen Code (eine Art von Code) testen, was mit Entity fast unmöglich ist Framework allein. Andererseits verliere ich die Fähigkeit, umfangreiche Abfragen durchzuführen und Beziehungen semantisch zu pflegen (aber selbst wenn ich vollen Zugriff auf diese Funktionen habe, habe ich immer das Gefühl, auf Eierschalen um EF oder einem anderen von mir gewählten ORM herumzulaufen Da ich nie weiß, welche Methoden die IQueryable-Implementierung möglicherweise unterstützt oder nicht unterstützt, kann sie das Hinzufügen zu einer Navigationseigenschaftensammlung als Erstellung oder lediglich als Verknüpfung interpretieren, ob sie verzögert oder eifrig geladen wird oder nicht Standard usw. Vielleicht ist das zum Besseren. Das objektrelationale "Mapping" ohne Impedanz ist etwas Mythologisches - vielleicht wurde die neueste Version von Entity Framework deshalb mit dem Codenamen "Magic Unicorn" versehen.
Das Abrufen Ihrer Entitäten über abfragespezifische Datenabrufmethoden bedeutet jedoch, dass Ihre Komponententests nun im Wesentlichen White-Box-Tests sind und Sie in dieser Angelegenheit keine Wahl mehr haben, da Sie im Voraus genau wissen müssen, welche Repository-Methode die zu testende Einheit verwenden soll rufen Sie an, um es zu verspotten. Und Sie testen die Abfragen selbst immer noch nicht, es sei denn, Sie schreiben auch Integrationstests.
Dies sind komplexe Probleme, die eine komplexe Lösung erfordern. Sie können es nicht beheben, indem Sie einfach so tun, als ob alle Ihre Entitäten separate Typen ohne Beziehungen zwischen ihnen wären, und sie jeweils in ihr eigenes Repository atomisieren. Na du kannst , aber es ist scheiße.
Update: Ich hatte einige Erfolge mit dem Effort- Anbieter für Entity Framework. Effort ist ein In-Memory-Anbieter (Open Source), mit dem Sie EF in Tests genau so verwenden können, wie Sie es für eine echte Datenbank verwenden würden. Ich denke darüber nach, alle Tests in diesem Projekt, an denen ich arbeite, zu ändern, um diesen Anbieter zu verwenden, da es die Dinge so viel einfacher zu machen scheint. Es ist die einzige Lösung, die ich bisher gefunden habe und die alle Probleme anspricht, über die ich mich zuvor geärgert habe. Das Einzige ist, dass es beim Starten meiner Tests eine leichte Verzögerung gibt, da die In-Memory-Datenbank erstellt wird (hierfür wird ein anderes Paket namens NMemory verwendet), aber ich sehe dies nicht als echtes Problem an. Es gibt einen Artikel in Code Project , in dem es darum geht, Effort (im Vergleich zu SQL CE) zum Testen zu verwenden.
DbContext
. Unabhängig davon konnte man sich immer lustig machen DbSet
, und das ist sowieso das Fleisch von Entity Framework. DbContext
ist kaum mehr als eine Klasse, um Ihre DbSet
Eigenschaften (Repositorys) an einem Ort (Arbeitseinheit) unterzubringen, insbesondere in einem Unit-Testing-Kontext, in dem die gesamte Datenbankinitialisierung und das gesamte Verbindungsmaterial sowieso nicht gewünscht oder benötigt werden.
Der Grund, warum Sie das wahrscheinlich tun würden, ist, weil es ein wenig überflüssig ist. Entity Framework bietet Ihnen eine Fülle von Codierungs- und Funktionsvorteilen. Aus diesem Grund verwenden Sie es. Wenn Sie es dann in ein Repository-Muster packen und diese Vorteile verwerfen, können Sie auch eine andere Datenzugriffsebene verwenden.
Theoretisch halte ich es für sinnvoll, die Datenbankverbindungslogik zu kapseln, damit sie leichter wiederverwendbar ist. Wie der folgende Link zeigt, kümmern sich unsere modernen Frameworks jetzt im Wesentlichen darum.
ISessionFactory
und ISession
leicht zu verspotten ist), ist es mit DbContext
leider nicht so einfach ...
Ein sehr guter Grund , das Repository-Muster zu verwenden, besteht darin, die Trennung Ihrer Geschäftslogik und / oder Ihrer Benutzeroberfläche von System.Data.Entity zuzulassen. Dies hat zahlreiche Vorteile, darunter auch echte Vorteile beim Testen von Einheiten, indem er die Verwendung von Fakes oder Mocks zulässt.
Es gab Probleme mit doppelten, aber unterschiedlichen Entity Framework DbContext-Instanzen, wenn ein IoC-Container, der Repositorys pro Typ neu einrichtet () (z. B. ein UserRepository und eine GroupRepository-Instanz, die jeweils ein eigenes IDbSet aus DBContext aufrufen), manchmal mehrere Kontexte pro Anforderung verursacht (In einem MVC / Web-Kontext).
Meistens funktioniert es noch, aber wenn Sie darüber hinaus eine Serviceebene hinzufügen und diese Services davon ausgehen, dass mit einem Kontext erstellte Objekte in einem anderen Kontext korrekt als untergeordnete Auflistungen an ein neues Objekt angehängt werden, schlägt dies manchmal fehl und manchmal nicht. ' t abhängig von der Geschwindigkeit der Commits.
Nach dem Ausprobieren des Repository-Musters für ein kleines Projekt rate ich dringend davon ab, es zu verwenden. Nicht, weil es Ihr System kompliziert, und nicht, weil das Verspotten von Daten ein Albtraum ist, sondern weil Ihre Tests unbrauchbar werden !!
Durch das Verspotten von Daten können Sie Details ohne Header hinzufügen, Datensätze hinzufügen, die gegen Datenbankeinschränkungen verstoßen, und Entitäten entfernen, deren Entfernung die Datenbank ablehnen würde. In der Praxis kann eine einzige Aktualisierung mehrere Tabellen, Protokolle, Verlaufsdaten, Zusammenfassungen usw. sowie Spalten wie das Feld für das Datum der letzten Änderung, automatisch generierte Schlüssel und berechnete Felder betreffen.
Kurz gesagt, Ihr Test für eine echte Datenbank liefert echte Ergebnisse, und Sie können nicht nur Ihre Dienste und Schnittstellen, sondern auch das Datenbankverhalten testen. Sie können überprüfen, ob Ihre gespeicherten Prozeduren mit Daten das Richtige tun, das erwartete Ergebnis zurückgeben oder ob der Datensatz, den Sie zum Löschen gesendet haben, wirklich gelöscht wurde! Solche Tests können auch Probleme wie das Vergessen, Fehler von gespeicherten Prozeduren auszulösen, und Tausende solcher Szenarien aufdecken.
Ich denke, das Entity Framework implementiert das Repository-Muster besser als alle Artikel, die ich bisher gelesen habe, und es geht weit über das hinaus, was sie erreichen wollen.
Repository war an jenen Tagen die beste Praxis, als wir XBase, AdoX und Ado.Net verwendeten, aber mit Entity !! (Repository über Repository)
Schließlich denke ich, dass zu viele Leute viel Zeit in das Erlernen und Implementieren von Repository-Mustern investieren und sich weigern, es loszulassen. Meistens, um sich selbst zu beweisen, dass sie ihre Zeit nicht verschwendet haben.
Das liegt an Migrationen: Es ist nicht möglich, Migrationen zum Laufen zu bringen, da sich die Verbindungszeichenfolge in der Datei web.config befindet. Der DbContext befindet sich jedoch in der Repository-Ebene. IDbContextFactory muss eine Konfigurationszeichenfolge für die Datenbank haben. Auf keinen Fall können Migrationen die Verbindungszeichenfolge aus web.config abrufen.
Es gibt Probleme, aber ich habe noch keine saubere Lösung dafür gefunden!