Wie funktioniert das Repository-Muster, wenn Entitäten miteinander verwandt sind?

8

Es gibt eine Frage zum IRepository und wofür es verwendet wird, hat eine scheinbar gute Antwort.

Mein Problem allerdings: Wie würde ich sauber mit Entitäten umgehen, die miteinander verwandt sind, und ist nicht IRepository dann nur eine Ebene ohne wirklichen Zweck?

Nehmen wir an, ich habe diese Geschäftsobjekte:

%Vor%

Es gibt Regeln:

  • Jede Region muss mindestens einen Ort haben
  • Neu erstellte Regionen werden mit einem Ort erstellt
  • Nein SELECT N + 1 bitte

Wie würde mein RegionRepository aussehen?

%Vor%

Wie Sie sehen, muss ein RegionRepository ebenfalls Standorte abrufen. In meinem Beispiel verwende ich Linq To Sql EntitySet / EntireyRef, aber jetzt muss Region mit Mapping Locations zu Business Objects umgehen (weil ich zwei Gruppen von Objekten, Business- und L2S-Objekten habe).

Soll ich das zu etwas umgestalten wie:

%Vor%

Nun habe ich meine Datenschicht schön in atomare Repositories aufgeteilt, die jeweils nur einen Typ haben. Ich feuere den Profiler und ... Whoops, SELECT N + 1. Weil jede Region den Standortdienst aufruft. Wir haben nur ein Dutzend Regionen und ungefähr 40 Standorte, also ist die natürliche Optimierung die Verwendung von DataLoadOptions . Das Problem ist, dass das RegionRepository nicht weiß, ob LocationRepository denselben DataContext verwendet oder nicht. Wir injizieren doch Fabriken hierhin, also könnte LocationRepository selbstständig werden. Und selbst wenn dies nicht der Fall ist - ich rufe eine Service-Methode auf, die Business-Objekte zur Verfügung stellt, so dass die DataLoadOptions trotzdem nicht verwendet werden dürfen.

Ah, ich habe etwas übersehen. IRepository soll eine Methode wie folgt haben:

%Vor%

Also würde ich jetzt

machen %Vor%

Das sieht gut aus. Zu Beginn. Bei der zweiten Inspektion habe ich separate Business- und L2S-Objekte, daher sehe ich immer noch nicht, wie dies SELECT N + 1 vermeidet, da Query nicht nur GetTable<DbLocation> zurückgeben kann.

Das Problem scheint zwei verschiedene Objektgruppen zu haben. Aber wenn ich Business Objects mit allen Attributen von System.Data.LINQ ausstatte ([Table], [Column] usw.), bricht das die Abstraktion und vereitelt den Zweck von IRepository. Da ich vielleicht auch ein anderes ORM verwenden möchte, müsste ich nun meine Business Entities mit anderen Attributen ausstatten (auch wenn die Business Entities in einer separaten .Business-Assembly sind, müssen die Konsumenten dies jetzt tun) Verweise auf alle ORMs auch auf die zu lösenden Attribute - yuck!).

Mir scheint, dass IRepository IService sein sollte, und die obige Klasse sollte so aussehen:

%Vor%

Dies löst auch ein Hühnerei-Problem:

  • DbRegion.ID wird von der Datenbank generiert (als newid ()) und IsDbGenerated = true wird gesetzt
  • DbRegion.DefaultLocationId ist eine nicht nullwertfähige GUID
  • DbRegion.DefaultLocationId ist ein FK in Location.ID
  • DbLocation.RegionId ist eine nicht nullbare GUID und ein FK in Region.ID

Dies ist ohne EntitySet praktisch unmöglich. Wenn Sie also die Datenintegrität in der Datenbank nicht opfern und in die Geschäftslogik verschieben, ist es unmöglich, die Verantwortung für Standorte außerhalb des Region-Anbieters zu behalten.

Ich sehe, wie dieser Beitrag als keine echte, subjektive und argumentative Frage gesehen werden kann. Gestatten Sie mir deshalb, eine objektive Frage zu formulieren:

  • Was genau soll das Repository Pattern abstrahieren?
  • Wie können Menschen in der realen Welt ihre Datenbankebene optimieren, ohne die Abstraktion zu unterbrechen, die das Repository-Muster erreichen soll?
  • Genauer gesagt, wie geht die reale Welt mit SELECT N + 1 und Fragen der Datenintegrität um?

Ich denke, meine eigentliche Frage ist:

  • Wenn bereits ein ORM (wie Linq To Sql) verwendet wird, ist DataContext nicht bereits mein Repository, und daher abstrahiert ein Repository oben auf DataContext genau dasselbe noch einmal ?
Michael Stum 12.11.2011, 08:21
quelle

2 Antworten

4

Beim Entwerfen Ihrer Repositories sollten Sie über den so genannten Aggregatstamm nachdenken. Im Wesentlichen bedeutet dies, dass, wenn eine Entität alleine in der Domäne existieren kann, sie mehr als nur ein eigenes Repository haben wird. In Ihrem Fall wäre das die Region.

Betrachten Sie das klassische Kunden / Auftrags-Szenario. Das Kundenrepository würde den Zugriff auf Bestellungen ermöglichen, da eine Bestellung ohne einen Kunden nicht existieren kann. Daher ist es unwahrscheinlich, dass Sie ein separates Bestellrepository benötigen, sofern Sie keinen gültigen Geschäftsfall haben.

In einer einfachen Anwendung mag Ihre Annahme richtig sein, aber denken Sie daran, dass Sie, solange Sie nicht eine Abstraktion Ihres L2S-Kontextes bereitstellen, Schwierigkeiten haben, effektive Komponententests durchzuführen. Kodierung gegen eine Schnittstelle, sei es eine IServiceX, IRepositoryX oder was auch immer, bietet Ihnen diese Ebene der Trennung.

Die Entscheidung, ob Service-Interfaces in das Design einfließen, hängt in der Regel wieder mit der Komplexität der Geschäftslogik und der Notwendigkeit einer erweiterbaren Api in diese Logik zusammen, die möglicherweise von mehreren unterschiedlichen Clients verwendet wird.

    
Darren Lewis 12.11.2011 09:06
quelle
1

Ich habe mehrere Gedanken über all das: 1. Das AFAIK-Repository-Muster wurde etwas früher als das ORM erfunden. Früher war es bei einfachen SQL-Abfragen eine gute Idee, das Repository zu implementieren und diesen abstrakten Code von der tatsächlich verwendeten Datenbank zu kaufen. 2. Ich könnte sagen, dass Repository jetzt nicht benötigt wird, aber leider kann ich aus meiner Erfahrung nicht sagen, dass jedes ORM Sie wirklich von allen Datenbankdetails abstrahieren kann. Z.B. Ich konnte nicht einmal ein ORM-Mapping erstellen und es nur mit jedem anderen DB-Server verwenden, den ORM zu unterstützen (insbesondere ich spreche über Microsoft EF). Wenn Sie also wirklich verschiedene Datenbankserver verwenden möchten, müssen Sie wahrscheinlich Repository verwenden. 3. Ein weiteres Problem ist sehr einfach: Code-Duplizierung. Sicher gibt es einige Abfragen, die Sie häufig Ihren Code aufrufen. Wenn Sie nur ORM als Repository beibehalten, dann werden Sie diese Abfragen duplizieren, so dass es besser ist, eine gewisse Abstraktionsebene über dem ORM-Container zu haben, der diese häufig verwendeten Abfragen enthält.

    
Vladimir Perevalov 12.11.2011 08:53
quelle