Dieser Artikel wurde maschinell übersetzt.

Data Points

Verkleinern von EF-Modellen mithilfe von gebundenen DDD-Kontexten

Julie Lerman

Julie LermanWenn Sie Modelle für den Einsatz mit der Entity Framework (EF) definieren, sind Entwickler oft alle Klassen in der gesamten Anwendung verwendet werden.Dies wird durch Erstellen eines neuen Datenbank erste Modells im EF-Designer und alle verfügbaren Tabellen und Ansichten aus der Datenbank auswählen.Für diejenigen unter Ihnen mit ersten Code Ihres Modells definieren könnte es bedeuten, DbSet Eigenschaften in einem einzigen DbContext für alle Klassen erstellen oder sogar unwissentlich einschließlich Klassen, die mit denen verbunden sind, auf die Sie ausgerichtet haben.

Wenn Sie mit einem großen Modell und eine große Anwendung arbeiten, gibt es zahlreiche Vorteile für die Gestaltung von kleineren, mehr-kompakt-Modelle, die auf eine bestimmte Anwendungsaufgaben ausgerichtet sind, anstatt ein einziges Modell für die gesamte Lösung.In diesem Artikel, ich werde stellen Ihnen ein Konzept von Domain-driven Design (DDD) — Kontext begrenzt — und zeigen Ihnen, wie es um eine gezielte Modells mit EF, Schwerpunkt dabei die größere Flexibilität der EF Code erste Funktion anzuwenden.Wenn Sie neu in DDD, Ansatz dies eine große um zu erfahren, selbst wenn Sie sind nicht vollständig zu DDD begehen.Und wenn Sie bereits DDD verwenden, profitieren Sie durch den Anblick, wie Sie EF während folgenden DDD-Methoden verwenden können.

Domain-Driven Design und beschränkten Rahmen

DDD ist ein ziemlich umfangreiches Thema, das eine ganzheitliche Sicht der Softwareentwicklung umfasst.Paul Rayner, DDD Werkstätten für Domain Sprache lehrt, (DomainLanguage.com), bringt es kurz und bündig:

"DDD befürwortet pragmatische, ganzheitliche und durchgängige Software-Design: Zusammenarbeit mit Domänenexperten reichhaltiger Domänenmodelle in die Software eingebettet – Modelle, mit deren Hilfe wichtige und komplexe Geschäftsprobleme zu lösen. "

DDD umfasst zahlreiche Softwaredesign patterns, von denen — Kontext begrenzt — eignet sich perfekt für die Arbeit mit EF.Berandete Kontext konzentriert sich auf die Entwicklung von kleiner Models, die als Ziel für bestimmte Operationen in Ihre Business-Domain.In seinem Buch "Driven Design" (Addison-Wesley, 2003), Eric Evans erklärt, dass Kontext begrenzt die Anwendbarkeit eines bestimmten Modells "begrenzt.Umgebenden Kontext bietet Teammitgliedern eine klare gemeinsame Verständnisgrundlage was konsistent sein muss und was eigenständig entwickeln kann."

Kleinere Modelle bieten zahlreiche Vorteile, so dass Mannschaften klare Grenzen in Bezug auf Design und Entwicklung Verantwortung definieren.Sie führen auch zu besseren Wartbarkeit — da ein Kontext eine kleinere Fläche hat, haben Sie weniger Nebenwirkungen zu befürchten, wenn Sie Änderungen vornehmen.Es gibt darüber hinaus ein Leistungsvorteil, wenn EF im Arbeitsspeicher Metadaten für ein Modell erstellt, wenn sie zuerst in den Speicher geladen wird.

Da ich begrenzte Kontexten mit EF DbContext Gebäude bin, habe ich meine DbContexts als "DbContexts begrenzt." bezeichnet Die beiden sind jedoch nicht tatsächlich gleichwertig: DbContext ist eine Klassenimplementierung während Kontext begrenzt das größere Konzept innerhalb der kompletten Design-Prozess umfasst.Ich werde daher bezeichnen, meine DBContexts als "eingeschränkt" oder "konzentriert."

Eine typische EF DbContext zu vergleichen Kontext begrenzt

Während DDD auf große Anwendungsentwicklung in komplexen geschäftlichen Bereichen am häufigsten angewendet wird, können kleinere apps auch von vielen seiner Lektionen profitieren.Im Interesse dieser Erklärung konzentriere ich mich auf eine Anwendung, die gezielt an eine bestimmte Sub-Domain: Tracking-Vertrieb und marketing für ein Unternehmen.Objekte mit dieser Anwendung können zwischen Kunden, Bestellungen und Einzelposten, Produkte, marketing, Verkäufer und sogar Mitarbeiter liegen.Normalerweise würde ein DbContext definiert werden, um DbSet Eigenschaften für jede Klasse in der Projektmappe enthalten, die in der Datenbank gespeichert werden, wie in muss Abbildung 1.

Abbildung 1 typische DbContext mit allen Domain-Klassen in die Lösung

public class CompanyContext : DbContext
{
  public DbSet<Customer> Customers { get; set; }
  public DbSet<Employee>  Employees { get; set; }
  public DbSet<SalaryHistory> SalaryHistories { get; set; }
  public DbSet<Order> Orders { get; set; }
  public DbSet<LineItem> LineItems { get; set; }
  public DbSet<Product> Products { get; set; }
  public DbSet<Shipment> Shipments { get; set; }
  public DbSet<Shipper> Shippers { get; set; }
  public DbSet<ShippingAddress> ShippingAddresses { get; set; }
  public DbSet<Payment> Payments { get; set; }
  public DbSet<Category> Categories { get; set; }
  public DbSet<Promotion> Promotions { get; set; }
  public DbSet<Return> Returns { get; set; }
  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {
    // Config specifies a 1:0..1 relationship between Customer and ShippingAddress
    modelBuilder.Configurations.Add(new ShippingAddressMap());
  }
}

Stell dir vor das wäre eine viel weiter reichenden Anwendung mit Hunderten von Klassen. Und Sie haben vielleicht auch Fluent API Konfigurationen für einige dieser Klassen. Das macht eine Menge Code durch waten und in einer einzelnen Klasse verwalten. Mit solch einer großen Anwendung möglicherweise Entwicklung Teams aufgeteilt werden. Mit diesem einzigen unternehmensweiten DbContext müsste jedes Team eine Teilmenge des Code base, die über ihre Verantwortung hinausgeht. Und des Teams Änderungen an diesem Zusammenhang könnte ein anderes Team Arbeit beeinflussen.

Es gibt interessante Fragen, die Sie sich über diese unscharf, übergreifende DbContext stellen konnte. Beispielsweise haben Benutzer im Bereich der Anwendung, die auf die marketing-Abteilung zu arbeiten mit Mitarbeiterdaten Gehalt Geschichte müssen? Braucht die Versandabteilung auf dem Niveau der Details zu einem Kunden als ein Kundendienstmitarbeiter zugreifen? Würde jemand in der Versandabteilung müssen einen Kundendatensatz bearbeiten? Für die häufigsten Szenarien, die Antwort auf diese Fragen wäre in der Regel keine, und dies könnte helfen, Sie sehen warum es könnte sinnvoll sein, mehrere DbContexts, die kleinere Gruppen von Domain-Objekten zu verwalten.

Eine fokussierte DbContext für die Versandabteilung

Wie DDD arbeiten mit kleineren, mehr ausgerichteten Modellen mit klar definierten Kontextgrenzen empfiehlt, lassen Sie uns engen Rahmen dieses DbContext Ziel Versand Abteilung Funktionen und nur diese für die entsprechenden Aufgaben benötigten Klassen. Daher können Sie einige Eigenschaften des DbSet von den DbContext, so dass nur diejenigen, die Sie benötigen, zur Unterstützung von Business-Funktionen mit Bezug zu Versand entfernen. Ich habe genommen gibt, Promotions, Kategorien, Zahlungen, Mitarbeiter und SalaryHistories:

public class ShippingDeptContext : DbContext
{
  public DbSet<Shipment> Shipments { get; set; }
  public DbSet<Shipper> Shippers { get; set; }
  public DbSet<Customer> Customers { get; set; }
  public DbSet<ShippingAddress> ShippingAddresses { get; set; }
  public DbSet<Order> Order { get; set; }
  public DbSet<LineItem> LineItems { get; set; }
  public DbSet<Product> Products { get; set; }
}

EF Code ersten verwendet die Details der ShippingContext um das Modell abzuleiten. Abbildung 2 zeigt eine Visualisierung des Modells, die von dieser Klasse, die ich erzeugt mit dem Entity Framework Power Tools Beta 2 erstellt werden. Jetzt beginnen wir das Modell Feinabstimmung.

Visualized Model from First Pass at the ShippingContext
Abbildung 2 visualisiert Modell aus der First-Pass bei der ShippingContext

Tuning der DbContext und mehr-bezogene Klassen erstellen

Es gibt noch weitere Klassen in das Modell einbezogen, als ich für den Versand angegeben. Gemäß der Konvention enthält erste Code alle Klassen, die von anderen Klassen im Modell erreichbar sind. Das ist, warum Kategorie und Zahlung zeigte sich auch, wenn ich ihre DbSet Eigenschaften entfernt. Also erkläre ich den DbContext, Kategorie und Zahlung zu ignorieren:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Ignore<Category>();
  modelBuilder.Ignore<Payment>();
  modelBuilder.Configurations.Add(new ShippingAddressMap());
}

Dadurch wird die Kategorie und Zahlung in das Modell gezogen bekommen nicht nur, weil sie auf Produkt und Bestellung verwandt sind.

Sie können diese DbContext Klasse ohne Auswirkungen auf das resultierende Modell noch mehr verfeinern. Mit diesen Eigenschaften DbSet ist es möglich, jede dieser sieben Gruppen von Daten in Ihrer Anwendung explizit abgefragt. Aber wenn man die Klassen und deren Beziehungen denkt, könnte man schließen, dass in diesem Zusammenhang es nie notwendig, um die Abfrage für den Versand werden­direkt — es immer zusammen mit den Kundendaten abgerufen werden kann. Auch mit keinen ShippingAddresses DbSet können Sie auf dieselbe Konvention verlassen, die in der Kategorie und Zahlung Lieferadresse in das Modell wegen seiner Beziehung zu Kunden ziehen automatisch gezogen. Also können Sie die ShippingAddresses-Eigenschaft entfernen, ohne dabei die Datenbank-Mappings zur Lieferadresse. Sie können möglicherweise rechtfertigen, andere entfernen, aber konzentrieren wir uns auf eben diese ein:

public class ShippingContext : DbContext
{
  public DbSet<Shipment> Shipments { get; set; }
  public DbSet<Shipper> Shippers { get; set; }
  public DbSet<Customer> Customers { get; set; }
  // Public DbSet<ShippingAddress> ShippingAddresses { get; set; }
  public DbSet<Order> Order { get; set; }
  public DbSet<LineItem> LineItems { get; set; }
  public DbSet<Product> Products { get; set; }
  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  { ...
}
}

Im Rahmen der Verarbeitung Sendungen brauche ich nicht wirklich eine vollständige Customer-Objekt, eine vollständige Order-Objekt oder ein voller LineItem-Objekt. Ich brauche nur das Produkt versandt werden, die Menge (von LineItem), den Namen des Kunden und Lieferadresse und Notizen, die an den Kunden bzw. die Bestellung angefügt werden können. Ich habe meine DBA eine Ansicht erstellen, die FrauenBootes Elemente zurückgibt — mit ShipmentId = 0 oder Null. In der Zwischenzeit kann ich optimierte Klasse definieren, die zugeordnet werden, auf diese Ansicht mit der relevanten Eigenschaften, dass ich rechnen müssen:

[Table("ItemsToBeShipped")]
public class ItemToBeShipped
{
  [Key]
  public int LineItemId { get; set; }
  public int OrderId { get; set; }
  public int ProductId { get; set; }
  public int OrderQty { get; private set; }
  public OrderShippingDetail OrderShippingDetails { get; set; }
}

Die Logik der Verarbeitung Sendungen erfordert für die ItemToBeShipped Abfragen und dann immer was auch immer ich könnte zusammen mit dem Kunden und der Lieferadresse brauchen Bestelldetails. Ich konnte meine DbContext Definition um mir erlauben, einen Graphen ab dieser neuen Art einschließlich der Bestellung, Kunden- und Versand Abfragen reduzieren­Adresse. Jedoch weil ich weiß, EF würde dies mit einer SQL-Abfrage erreichen entwickelt, um die Ergebnisse zusammenfassen und wiederholte Bestellung, Kunden und Versand zurück­Adressdaten zusammen mit jeder Posten, ich lasse den Programmierer für eine Bestellung Abfragen und bringen wieder ein Diagramm mit dem Kunden und der Lieferadresse. Aber wieder, ich brauche nicht alle Spalten aus der Tabelle, sodass ich werde eine Klasse erstellen, die gezielter für die Versandabteilung, einschließlich der Informationen, die auf ein Manifest Versand gedruckt werden konnte. Die Klasse ist die Klasse OrderShippingDetail in Abbildung 3.

Abbildung 3 die OrderShippingDetail-Klasse

[Table("Orders")]
public class OrderShippingDetail
{  
  [Key]
  public int OrderId { get; set; }
  public DateTime OrderDate { get; set; }
  public Nullable<DateTime> DueDate { get; set; }
  public string SalesOrderNumber { get; set; }
  public string PurchaseOrderNumber { get; set; }
  public Customer Customer { get; set; }
  public int CustomerId { get; set; }
  public string Comment { get; set; }
  public ICollection<ItemToBeShipped> OpenLineItems { get; set; }
}

Beachten Sie, dass meine ItemToBeShipped-Klasse eine Navigationseigenschaft für OrderShippingDetail besitzt und OrderShippingDetail eine für den Kunden hat. Die Navigationseigenschaften hilft mir mit Diagrammen beim Abfragen und speichern.

Es gibt Gust für dieses Rätsel. Die Versandabteilung benötigen um Elemente zu kennzeichnen, als geliefert und die LineItems-Tabelle hat eine ShipmentId-Spalte, die verwendet wird, um eine Position einer Lieferung zu binden. Die app müssen das ShipmentId-Feld aktualisieren, wenn ein Einzelteil versendet wird. Ich erstelle eine einfache Klasse um kümmern uns diese Aufgabe, anstatt auf die LineItem-Klasse, die für den Vertrieb verwendet wird:

[Table("LineItems")]
public class LineItemShipment
{
  [Key]
  public int LineItemId { get; set; }
  public int ShipmentId { get; set; }
}

Wenn ein Element versandt wurde, können erstellen eine neue Instanz dieser Klasse mit geeigneten Werten und die Anwendung der LineItem in der Datenbank zu aktualisieren. Entwerfen Sie Ihre Anwendung die Klasse nur für diesen Zweck verwendet werden. Wenn Sie versuchen, eine Verwendung dieser Klasse statt einem dieser Konten für NULL-Tabellenfelder z. B. Bestell-Nr LineItem einzufügen, wird die Datenbank eine Ausnahme ausgelöst.

Nachdem einige weitere Feinabstimmung, ist meine ShippingContext jetzt wie folgt definiert:

public class ShippingContext : DbContext
{
  public DbSet<Shipment> Shipments { get; set; }
  public DbSet<Shipper> Shippers { get; set; }
  public DbSet<OrderShippingDetail> Order { get; set; }
  public DbSet<ItemToBeShipped> ItemsToBeShipped { get; set; }
  protected override void OnModelCreating(DbModelBuilder modelBuilder)
  {
    modelBuilder.Ignore<LineItem>();
    modelBuilder.Ignore<Order>();
    modelBuilder.Configurations.Add(new ShippingAddressMap());
  }
}

Mit die Entity Framework Power Tools Beta 2 wieder eine EDMX erstellen, ich sehe im Fenster Modellbrowser (Abbildung 4), dass Code erste folgert, dass das Modell enthält die vier Klassen angegeben, durch den DbSets, als auch die Kunden und die Lieferadresse, die durch Navigationseigenschaften der OrderShippingDetail-Klasse entdeckt wurden.

Model Browser View of ShippingContext Entities as Inferred by Code First
Abbildung 4-Modell-Browser-Ansicht des ShippingContext Entitäten wie vom Code zuerst abgeleitet

Fokussierte DbContext und Datenbankinitialisierung

Wenn Sie eine kleinere DbContext verwenden, die spezifische Bounded Contexts in Ihrer Anwendung unterstützt, ist es wichtig im Geist zwei EF Code erste Standardverhalten in Bezug auf die Datenbankinitialisierung behalten.

Die erste ist, dass für eine Datenbank mit dem Namen des Kontexts erste Code aussieht. Das ist nicht wünschenswert, wenn Ihre Anwendung eine ShippingContext, eine CustomerContext und eine SalesContext hat. Stattdessen wollen Sie alle DbContexts auf derselben Datenbank hinzu.

Das zweite Standardverhalten zu berücksichtigen ist, dass der erste Code das Modell durch eine DbContext hergeleitet verwendet wird, um das Datenbankschema zu definieren. Aber jetzt haben Sie eine DbContext, die nur ein Segment der Datenbank darstellt. Aus diesem Grund möchten Sie nicht die DbContext Klassen Datenbankinitialisierung auslösen.

Es ist möglich, beide Probleme in den Konstruktor der Kontextklasse jeden zu lösen. Beispielsweise können hier in der ShippingContext-Klasse haben Sie den Konstruktor angeben, die DPSalesDatabase und Datenbankinitialisierung zu deaktivieren:

public ShippingContext() : base("DPSalesDatabase")
{
  Database.SetInitializer<ShippingContext>(null);
}

Haben Sie eine Menge von DbContext Klassen in Ihrer app, wird dies jedoch ein Problem Wartung werden. Eine bessere Muster ist eine Basisklasse angeben, die deaktiviert die Datenbankinitialisierung und setzt die Datenbank zur gleichen Zeit:

public class BaseContext<TContext>
  DbContext where TContext : DbContext
{
  static BaseContext()
  {
    Database.SetInitializer<TContext>(null);
  }
  protected BaseContext() : base("DPSalesDatabase")
  {}
}

Jetzt können meine verschiedenen Kontextklassen der BaseContext statt mit jeweils eigener Konstruktor implementieren:

public class ShippingContext:BaseContext<ShippingContext>

Wenn du Neuentwicklung machst und lassen erste Code erstellen oder die Ihre Klassen-Datenbank migrieren möchten, müssen Sie erstellen eine "Uber-Modell" mit einer DbContext, die enthält alle Klassen und Beziehungen, um ein vollständiges Modell zu bauen, das die Datenbank darstellt. Dabei muss jedoch nicht von BaseContext erben. Wenn Sie Ihre Klassenstrukturen ändern, können Sie Code ausführen, der Uber-Kontext zur Initialisierung der Datenbank erstellen oder Migration der Datenbank verwendet.

Ausübung der fokussierten DbContext

Alle in diesem Ort habe ich einige automatisierte Integrationstests, um die folgenden Aufgaben ausführen:

  • Rufen Sie offene Posten.
  • Rufen Sie OrderShippingDetails zusammen mit den Kunden und Versand der Daten für Aufträge mit FrauenBootes Posten.
  • Rufen Sie eine FrauenBootes Position ab und erstellen Sie eine neue Lieferung. Legen Sie die Sendung auf dem Posten und die neue Sendung beim Aktualisieren der Posten in der Datenbank mit der neuen Lieferung Schlüsselwert in die Datenbank eingefügt.

Dies sind die wichtigsten Funktionen benötigten für Versand Produkte an Kunden durchzuführen, die sie bestellt haben. Alle Tests bestehen, überprüfen, dass meine DbContext erwartungsgemäß funktioniert. Die Tests sind im Download Beispielcodes in diesem Artikel enthalten.

Zusammenfassung

Ich habe nicht nur eine DbContext, die speziell auf die Unterstützung der Versand Aufgaben ausgerichtet ist, aber denken sie durch auch half mir erstellen Sie effizientere Domänenobjekte mit diesen Aufgaben verwendet werden. Mit DbContext mein Kontext begrenzt mit meinem Versand Subdomäne auf diese Weise ausrichten bedeutet ich habe nicht waten durch einen Morast der Code auf das Versand-Abteilung an die Anwendung, und ich kann tun, was ich brauche um die spezialisierte Domänenobjekte ohne Auswirkungen auf andere Bereiche der Entwicklungsaufwand.

Sehen Sie weitere Beispiele für die Fokussierung DbContext unter Verwendung des Konzepts der DDD begrenzt Kontext in dem Buch "Programming Entity Framework: DbContext"(O' Reilly Media, 2011), die ich mit Rowan Miller zusammen.

Julie Lerman ist als Microsoft MVP, .NET-Mentor und Unternehmensberaterin tätig und wohnt in den Bergen von Vermont. Sie hält bei User Groups und Konferenzen in der ganzen Welt Vorträge zum Thema Datenzugriff und anderen Microsoft .NET-Themen. Julie Lerman führt unter thedatafarm.com/blog einen Blog. Sie ist die Verfasserin von „Programming Entity Framework“ (2010) sowie der Ausgaben „Code First“ (2011) und „DbContext“ (2012). Alle Ausgaben sind im Verlag O’Reilly Media erschienen. Folgen Sie ihr auf Twitter unter twitter.com/julielerman.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: Paul Rayner