Warum sollte ich das Repository-Muster nicht mit Entity Framework verwenden?

203

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?

StringBuilder
quelle
60
Es war ein Trick Frage
Omu
2
Ich hätte dem Interviewer wahrscheinlich geantwortet, dass Microsoft das Repository-Muster sehr oft verwendet, während sie das Entity-Framework demonstrieren: | .
Laurent Bourgault-Roy
1
Was war der Grund für den Interviewer, warum es keine gute Idee war?
Bob Horn
3
Die lustige Tatsache ist, dass die Suche nach "Repository-Muster" in Google die Ergebnisse liefert, die sich hauptsächlich auf das Entity Framework und die Verwendung des Musters mit EF beziehen.
Arseni Mourzenko
2
Überprüfen Sie den Blog von ayende ayende.com/blog . Basierend auf dem, was ich weiß, verwendete er das Repository-Muster, gab es aber schließlich zugunsten des Abfrageobjekt-Musters auf
Jaime Sangcap,

Antworten:

99

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 DbContextmit einem Repository ist die Testbarkeit der Einheiten . Sie können über eine IRepositorySchnittstelle verfügen , für die zwei Implementierungen vorhanden sind, eine (das echte Repository) DbContext, mit der mit der Datenbank gesprochen wird, und die zweite, FakeRepositorydie speicherinterne Objekte / verspottete Daten zurückgeben kann. Dies macht Ihre IRepositoryUnit 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
  }    
}
Shyju
quelle
3
Ich habe nicht gesagt , es wird nicht funktionieren, ich bin auch mit Repository - Muster mit EF gearbeitet, aber heute habe ich wurde gefragt , warum es nicht gut ist , das Muster mit DataBase zu verwenden, Anwendung , die Datenbank mit
2
Ok, da dies die beliebteste Antwort ist, wähle ich sie als Richtige Antwort
65
Wann war das letzte Mal das beliebteste == richtig?
HDave
14
DbContext ist bereits ein Repository, das Repository soll eine Abstraktion auf niedriger Ebene sein. Wenn Sie verschiedene Datenquellen abstrahieren möchten, erstellen Sie Objekte, um diese darzustellen.
Daniel Little
7
ColacX. Wir haben genau das versucht - DBcontext direkt in der Controller-Ebene - und kehren zum Repo-Muster zurück. Mit dem Repo-Muster gingen die Unit-Tests von massiven DbContext-Verspottungen aus, die ständig fehlschlugen. EF war schwierig zu verwenden, spröde und kostete Stunden der Forschung für EF-Nuancen. Wir haben jetzt kleine einfache Mocks des Repos. Der Code ist sauberer. Die Arbeitsteilung ist klarer. Ich bin nicht mehr mit der Masse einverstanden, dass EF bereits ein Repo-Muster ist und die Einheit bereits testbar ist.
Rhyous
434

Der beste Grund, das Repository-Muster nicht mit Entity Framework zu verwenden? Entity Framework implementiert bereits ein Repository-Muster. DbContextist Ihre UOW (Unit of Work) und jede DbSetist 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.

Chris Pratt
quelle
68
Dies scheint die einzig richtige Antwort zu sein.
Mike Chamberlain
10
Sie können spotten DbContextin 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 DbSets, da DbSetGeräte ein iterface, IDbSet.
Chris Pratt
14
@TheZenker, Sie haben möglicherweise nicht genau das Repository-Muster befolgt. Der strengste Unterschied ist der Rückgabewert. Repositorys geben abfragbare Daten zurück, wohingegen Services Enumerables zurückgeben sollten. Sogar das ist nicht wirklich so schwarz und weiß, da es dort einige Überlappungen gibt. Es geht mehr darum, wie Sie es benutzen. Ein Repository sollte nur die Menge aller Objekte zurückgeben, die Sie dann weiter abfragen, während der Service die endgültige Datenmenge zurückgeben sollte und keine weitere Abfrage unterstützen sollte.
Chris Pratt
10
Auf die Gefahr, egoistisch zu klingen: Sie liegen falsch. Was die offiziellen Tutorials anbelangt, hat Microsoft die Verwendung von Repositorys aus dem, was ich seit EF6 gesehen habe, aufgegeben. In Bezug auf das Buch kann ich nicht sagen, warum der Autor sich für die Verwendung von Repositories entschieden hat. Als jemand in den Gräben, der umfangreiche Anwendungen erstellt, kann ich nur sagen, dass die Verwendung des Repository-Musters mit Entity Framework ein Wartungs-Albtraum ist. Sobald Sie sich in etwas Komplexeres als eine Handvoll Repositories begeben, verbringen Sie viel Zeit mit der Verwaltung Ihrer Repositories / Arbeitseinheiten.
Chris Pratt
6
Normalerweise habe ich nur einen Dienst pro Datenbank oder Zugriffsmethode. Ich verwende generische Methoden, um mehrere Entitätstypen aus demselben Methodensatz abzufragen. Ich verwende Ninject, um meinen Kontext in meinen Dienst und dann meinen Dienst in meine Controller einzufügen, damit alles ordentlich und ordentlich ist.
Chris Pratt
45

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.

luksan
quelle
3
Alle Architekturartikel ohne Unit-Test werden automatisch in den Papierkorb verschoben. Ein Punkt des Repository-Musters besteht darin, einige Testfähigkeiten zu erlangen.
Sleeper Smith
3
Sie können immer noch Komponententests durchführen, ohne den EF-Kontext (der bereits ein Repository ist) einzuschließen. Sie sollten Unit-Tests für Ihre Domain / Services durchführen, keine Datenbankabfragen (es handelt sich um Integrationstests).
Daniel Little
2
Die Testbarkeit von EF hat sich in Version 6 erheblich verbessert. Sie können jetzt vollständig verspotten DbContext. Unabhängig davon konnte man sich immer lustig machen DbSet, und das ist sowieso das Fleisch von Entity Framework. DbContextist kaum mehr als eine Klasse, um Ihre DbSetEigenschaften (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.
Chris Pratt
Der Verlust der verwandten Entitätsnavigation ist schlecht und entspricht dem OOP des Zählers. Sie haben jedoch mehr Kontrolle darüber, was abgefragt wird.
Alireza
Bis zum Testen hat EF Core einen langen Weg hinter sich, um bei Sqlite-Anbietern In-Memory und In-Memory Unit-Tests zu ermöglichen. Rufen Sie docker auf, wenn Sie Integrationstests benötigen, um Tests für eine containerisierte Datenbank auszuführen.
Sudhanshu Mishra
16

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.

Neun Schwänze
quelle
Könnten Sie bitte einige der Vorteile für "Entity Framework bietet Ihnen eine Fülle von Codierungs- und Funktionsvorteilen" erläutern?
ManirajSS
2
das meinte er. var id = Entity.Where (i => i.Id == 1337) .Single () hat dies gekapselt und in ein Repository eingeschlossen. Sie können im Grunde keine solche Abfragelogik von außen ausführen, was Sie entweder dazu zwingt, A weiteren Code hinzuzufügen das Repository und die Schnittstelle zum Abrufen der ID. B den Entitätskontext aus dem Repository zurückgeben, damit Sie die Abfragelogik schreiben können (was nur Unsinn ist)
ColacX
14

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.

Überdenken des Repository-Musters

Pracht
quelle
Ich mochte den Artikel, aber IMHO für Enterprise-Apps, die Abstraktionsebene zwischen DAL und Bl MUST Have Feature, da man nicht genau wissen konnte, was morgen verwendet wird. Aber danke, dass du den Link geteilt
1
Während ich persönlich denke, dass es z. B. für NHibernate gilt ( ISessionFactoryund ISessionleicht zu verspotten ist), ist es mit DbContextleider nicht so einfach ...
Patryk Ćwiek
6

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.

James Culshaw
quelle
Ich stimme dieser Antwort zu. Meine Repositorys sind im Grunde nur Erweiterungsmethoden, die nichts anderes tun, als Ausdrucksbäume zu erstellen. Über eine SEHR einfache Abstraktion, die einfach generische Funktionen direkt über dbcontext bereitstellt. Der einzige wirkliche Zweck der Abstraktion besteht darin, die IoC ein wenig zu vereinfachen. Ich denke, die Leute versuchen in ihren Repositories Dinge zu tun, die sie nicht tun sollten. Sie erstellen Repos pro Entität oder fügen dort Geschäftslogik ein, die in der Serviceschicht enthalten sein soll. Sie brauchen eigentlich immer nur ein einfaches generisches Repo. Es ist nicht notwendig, bietet nur eine konsistente Schnittstelle.
Brandon
Eine weitere Sache, die ich nur hinzufügen wollte. Ja, CQRS ist in den meisten Fällen eine überlegene Methode. Für einige Kunden, bei denen ich gearbeitet habe, wenn die Datenbank-Leute nicht gut mit Entwicklern zusammenarbeiten (was besonders bei Banken häufiger vorkommt als man denkt), ist EF über SQL die beste Option. In diesem speziellen Szenario ist das Repository-Muster sinnvoll, wenn Sie absolut keine Kontrolle über Ihre Datenbank haben. Weil es der Datenstruktur sehr ähnlich ist und einfach zu übersetzen ist, was in der Datenbank vor sich geht und umgekehrt. Es ist meiner Meinung nach eine politische und logistische Entscheidung. Um die DB-Götter zu besänftigen.
Brandon
1
Ich fange tatsächlich an, meine früheren Meinungen dazu in Frage zu stellen. EF ist ein kombiniertes Unit-of-Work- und Repository-Muster. Wie Chris Pratt oben mit EF6 erwähnt hat, können Sie die Context- und DbSet-Objekte leicht verspotten. Ich bin immer noch der Meinung, dass der Datenzugriff in Klassen eingeschlossen werden sollte, um die Geschäftslogikklassen vor dem eigentlichen Datenzugriffsmechanismus zu schützen, aber es scheint übertrieben, EF mit einem anderen Repository und einer Unit of Work-Abstraktion zu verbinden.
James Culshaw
Ich denke nicht, dass dies eine gute Antwort ist, da Ihre Begründung nur darin besteht, dass es zahlreiche Vorteile gibt, wenn Sie nur einen auflisten. Der, den Sie auflisten, ist kein guter Grund, weil Sie eine speicherinterne Datenbank für Entitäten verwenden können Unit-Test.
Joel McBeth
@jcmcbeth Wenn Sie meinen Kommentar direkt über Ihrem ansehen, werden Sie feststellen, dass ich meine ursprüngliche Meinung in Bezug auf das Repository-Muster und EF geändert habe.
James Culshaw
0

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.

Mufasa
quelle
Ich bin auf dieses Problem in mehreren verschiedenen Projekten gestoßen.
ColacX
0

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.

Zawer Haroon
quelle
1
Mit der Ausnahme, dass Sie Ihr Datenbankverhalten NICHT in Komponententests testen möchten, da dies überhaupt nicht der Fall ist.
Mariusz Jamro
Ja, hier geht es um Integrationstests , und das ist in der Tat wertvoll, aber Unit-Tests sind völlig anders. Ihre Komponententests sollten niemals eine echte Datenbank treffen, aber Sie werden aufgefordert, Integrationstests hinzuzufügen, die dies tun.
Chris Pratt
-3

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!

Donner
quelle