Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Tipp
Dieser Inhalt ist ein Auszug aus dem eBook .NET Microservices Architecture for Containerized .NET Applications, verfügbar auf .NET Docs oder als kostenlose herunterladbare PDF, die offline gelesen werden kann.
Wenn Sie relationale Datenbanken wie SQL Server, Oracle oder PostgreSQL verwenden, empfiehlt es sich, die Persistenzschicht basierend auf Entity Framework (EF) zu implementieren. EF unterstützt LINQ und stellt Ihrem Modell stark typisierte Objekte zur Verfügung, und vereinfacht die Speicherung in Ihrer Datenbank.
Entity Framework hat eine lange Geschichte als Teil von .NET Framework. Wenn Sie .NET verwenden, sollten Sie auch Entity Framework Core verwenden, das unter Windows oder Linux auf die gleiche Weise wie .NET ausgeführt wird. EF Core ist ein vollständiges Umschreiben von Entity Framework, das mit einem viel kleineren Speicherbedarf und wichtigen Leistungsverbesserungen implementiert wird.
Einführung in Entity Framework Core
Entity Framework (EF) Core ist eine einfache, erweiterbare und plattformübergreifende Version der beliebten Entity Framework-Datenzugriffstechnologie. Es wurde mitte 2016 mit .NET Core eingeführt.
Da eine Einführung in EF Core bereits in der Microsoft-Dokumentation verfügbar ist, stellen wir hier einfach Links zu diesen Informationen bereit.
Weitere Ressourcen
Entity Framework Core
https://learn.microsoft.com/ef/core/Erste Schritte mit ASP.NET Core und Entity Framework Core mit Visual Studio
https://learn.microsoft.com/aspnet/core/data/ef-mvc/DbContext-Klasse
https://learn.microsoft.com/dotnet/api/microsoft.entityframeworkcore.dbcontextEf Core & EF6.x vergleichen
https://learn.microsoft.com/ef/efcore-and-ef6/index
Infrastruktur im Entity Framework Core aus DDD-Perspektive
Aus DDD-Sicht ist eine wichtige Funktion von EF die Möglichkeit, POCO-Domänenentitäten zu verwenden, die auch in der EF-Terminologie als POCO-Code-first-Entitäten bekannt sind. Wenn Sie POCO-Domänenentitäten verwenden, sind Ihre Domänenmodellklassen persistenz-unwissend und folgen den Prinzipien der Persistenz-Unwissenheit und der Infrastruktur-Unwissenheit .
Pro DDD-Muster sollten Sie das Domänenverhalten und die Regeln innerhalb der Entitätsklasse selbst kapseln, sodass sie invarianten, Überprüfungen und Regeln beim Zugriff auf eine beliebige Sammlung steuern kann. Daher ist es in DDD nicht empfehlenswert, einen öffentlichen Zugriff auf Auflistungen untergeordneter Entitäten oder Wertobjekte zu erlauben. Stattdessen möchten Sie Methoden verfügbar machen, die steuern, wie und wann Ihre Felder und Eigenschaftensammlungen aktualisiert werden können, und welches Verhalten und welche Aktionen auftreten sollten, wenn dies geschieht.
Seit EF Core 1.1 können Sie, um diese DDD-Anforderungen zu erfüllen, einfache Felder in Ihren Entitäten anstelle von öffentlichen Eigenschaften verwenden. Wenn Sie nicht möchten, dass auf ein Entitätsfeld extern zugegriffen werden kann, können Sie einfach das Attribut oder feld anstelle einer Eigenschaft erstellen. Ferner können Sie private Setter für Eigenschaften verwenden.
Auf ähnliche Weise ist jetzt ein schreibgeschützter Zugriff auf Auflistungen möglich, indem Sie eine als IReadOnlyCollection<T>
typisierte öffentliche Eigenschaft verwenden, die durch ein privates Feldelement für die Auflistung (wie List<T>
) in der Entität gestützt wird, das hinsichtlich der Persistenz auf EF vertraut. Frühere Versionen von Entity Framework benötigten Auflistungseigenschaften, um ICollection<T>
zu unterstützen, was bedeutete, dass jeder Entwickler, der die übergeordnete Entitätsklasse verwendete, Elemente mithilfe ihrer Eigenschaftenauflistung hinzufügen oder entfernen konnte. Diese Möglichkeit wäre gegen die empfohlenen Muster in DDD.
Wie im folgenden Codebeispiel gezeigt wird, können Sie eine private Auflistung beim Verfügbarmachen eines schreibgeschützten IReadOnlyCollection<T>
-Objekts verwenden:
public class Order : Entity
{
// Using private fields, allowed since EF Core 1.1
private DateTime _orderDate;
// Other fields ...
private readonly List<OrderItem> _orderItems;
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
protected Order() { }
public Order(int buyerId, int paymentMethodId, Address address)
{
// Initializations ...
}
public void AddOrderItem(int productId, string productName,
decimal unitPrice, decimal discount,
string pictureUrl, int units = 1)
{
// Validation logic...
var orderItem = new OrderItem(productId, productName,
unitPrice, discount,
pictureUrl, units);
_orderItems.Add(orderItem);
}
}
Auf die OrderItems
-Eigenschaft kann nur als nur lesbar mit IReadOnlyCollection<OrderItem>
zugegriffen werden. Dieser Typ ist schreibgeschützt, damit er vor regelmäßigen externen Aktualisierungen geschützt ist.
EF Core bietet eine Möglichkeit, das Domänenmodell der physischen Datenbank zuzuordnen, ohne das Domänenmodell "zu kontaminieren". Es handelt sich um reinen .NET-POCO-Code, da die Zuordnungsaktion auf der Persistenzschicht implementiert wird. In dieser Zuordnungsaktion müssen Sie die Felder-zu-Datenbank-Zuordnung konfigurieren. Im folgenden Beispiel der Methode OnModelCreating
aus OrderingContext
und der Klasse OrderEntityTypeConfiguration
weist der Aufruf SetPropertyAccessMode
EF Core an, auf die Eigenschaft OrderItems
über ihr Feld zuzugreifen.
// At OrderingContext.cs from eShopOnContainers
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// ...
modelBuilder.ApplyConfiguration(new OrderEntityTypeConfiguration());
// Other entities' configuration ...
}
// At OrderEntityTypeConfiguration.cs from eShopOnContainers
class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> orderConfiguration)
{
orderConfiguration.ToTable("orders", OrderingContext.DEFAULT_SCHEMA);
// Other configuration
var navigation =
orderConfiguration.Metadata.FindNavigation(nameof(Order.OrderItems));
//EF access the OrderItem collection property through its backing field
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
// Other configuration
}
}
Wenn Sie Felder anstelle von Eigenschaften verwenden, wird die OrderItem
Entität so persistiert, als ob sie eine List<OrderItem>
Eigenschaft hätte. Sie macht jedoch einen einzelnen Accessor, die AddOrderItem
Methode, zum Hinzufügen neuer Elemente zur Reihenfolge verfügbar. Daher sind Verhalten und Daten miteinander verknüpft und werden in jedem Anwendungscode konsistent sein, der das Domänenmodell verwendet.
Implementieren von benutzerdefinierten Repositorys mit Entity Framework Core
Auf Implementierungsebene ist ein Repository einfach eine Klasse mit Datenpersistenzcode, die durch eine Arbeitseinheit (DBContext in EF Core) koordiniert wird, wenn Aktualisierungen ausgeführt werden, wie in der folgenden Klasse gezeigt:
// using directives...
namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories
{
public class BuyerRepository : IBuyerRepository
{
private readonly OrderingContext _context;
public IUnitOfWork UnitOfWork
{
get
{
return _context;
}
}
public BuyerRepository(OrderingContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public Buyer Add(Buyer buyer)
{
return _context.Buyers.Add(buyer).Entity;
}
public async Task<Buyer> FindAsync(string buyerIdentityGuid)
{
var buyer = await _context.Buyers
.Include(b => b.Payments)
.Where(b => b.FullName == buyerIdentityGuid)
.SingleOrDefaultAsync();
return buyer;
}
}
}
Die IBuyerRepository
-Schnittstelle wird von der Domänenmodellebene als Vertrag übernommen. Die Repositoryimplementierung erfolgt jedoch auf der Persistenz- und Infrastrukturebene.
Der EF-DbContext stammt mittels Abhängigkeitseinfügung aus dem Konstruktor. Sie wird zwischen mehreren Repositories innerhalb desselben HTTP-Anforderungsbereichs gemeinsam genutzt, dank ihrer Standardlebensdauer (ServiceLifetime.Scoped
) im IoC-Container, die auch explizit mit services.AddDbContext<>
festgelegt werden kann.
Methoden zur Implementierung in einem Repository (Aktualisierungen oder Transaktionen im Vergleich zu Abfragen)
In jeder Repositoryklasse sollten Sie die Persistenzmethoden platzieren, die den Status der Entitäten aktualisieren, die in ihrem zugehörigen Aggregat enthalten sind. Denken Sie daran, dass zwischen einem Aggregat und dem zugehörigen Repository eine 1:1-Beziehung besteht. Berücksichtigen Sie, dass eine Aggregatwurzelentität möglicherweise untergeordnete Entitäten innerhalb ihres EF-Diagramms eingebettet haben kann. Ein Käufer könnte beispielsweise mehrere Zahlungsmethoden als verknüpfte untergeordnete Entitäten aufweisen.
Da der Ansatz für den Bestellungs-Microservice in eShopOnContainers ebenfalls auf CQS/CQRS basiert, werden die meisten Abfragen nicht in benutzerdefinierten Repositories implementiert. Entwickler haben die Freiheit, die Abfragen zu erstellen und verknüpfungen zu verbinden, die sie für die Präsentationsebene benötigen, ohne die einschränkungen, die von Aggregaten, benutzerdefinierten Repositorys pro Aggregat und DDD im Allgemeinen auferlegt werden. Die meisten von diesem Leitfaden vorgeschlagenen benutzerdefinierten Repositorys verfügen über mehrere Update- oder Transaktionsmethoden, aber nur die Abfragemethoden, die zum Abrufen von Daten erforderlich sind, um aktualisiert zu werden. Beispielsweise implementiert das Repository "BuyerRepository" eine FindAsync-Methode, da die Anwendung wissen muss, ob ein bestimmter Käufer vorhanden ist, bevor ein neuer Käufer im Zusammenhang mit der Bestellung erstellt wird.
Die tatsächlichen Abfragemethoden zum Abrufen von Daten zum Senden an die Präsentationsebene oder Client-Apps werden jedoch in den CQRS-Abfragen implementiert, die auf flexiblen Abfragen mit Dapper basieren.
Verwenden eines benutzerdefinierten Repositorys im Vergleich zur direkten Verwendung von EF DbContext
Die DbContext-Klasse von Entity Framework basiert auf den Mustern „Arbeitseinheit“ und „Repository“ und kann direkt über Ihren Code verwendet werden, beispielsweise über einen ASP.NET Core MVC-Controller. Die Muster „Arbeitseinheit“ und „Repository“ führen zu sehr einfachem Code wie im CRUD-Katalogmicroservice in eShopOnContainers. In Fällen, in denen Der einfachste Code möglich sein soll, sollten Sie die DbContext-Klasse wie viele Entwickler direkt verwenden.
Die Implementierung von benutzerdefinierten Repositorys bietet jedoch bei der Implementierung komplexerer Microservices oder Anwendungen mehrere Vorteile. Die Muster "Unit of Work" und "Repository" sollen die Persistenzschicht der Infrastruktur kapseln, damit sie von den Schichten der Anwendung und des Domänenmodells entkoppelt wird. Die Implementierung dieser Muster kann die Verwendung von Pseudorepositorys erleichtern, die den Zugriff auf die Datenbank simulieren.
In Abbildung 7-18 können Sie die Unterschiede zwischen der Nichtverwendung von Repositorys (direkte Verwendung des EF DbContext) und der Verwendung von Repositorys erkennen, wodurch es einfacher wird, diese Repositorys zu simulieren.
Abbildung 7-18. Verwenden von benutzerdefinierten Repositorys im Vergleich zu einem einfachen DbContext
Abbildung 7-18 zeigt, dass die Verwendung eines benutzerdefinierten Repositorys eine Abstraktionsebene hinzufügt, die verwendet werden kann, um das Testen durch Modellieren des Repositorys zu vereinfachen. Beim Mocking gibt es mehrere Alternativen. Sie könnten entweder nur Repositories simulieren oder eine ganze Arbeitseinheit simulieren. Üblicherweise reicht es aus, nur die Repositorys zu modellieren. Das komplexe Abstrahieren und Modellieren einer vollständigen Arbeitseinheit ist in der Regel nicht erforderlich.
Wenn wir uns später auf die Anwendungsebene konzentrieren, sehen Sie, wie Dependency Injection in ASP.NET Core funktioniert und wie es bei der Verwendung von Repositorys implementiert wird.
Kurz gesagt, mit benutzerdefinierten Repositorys können Sie Code einfacher mit Komponententests testen, die nicht vom Datenebenenstatus betroffen sind. Wenn Sie Tests ausführen, die auch über das Entity Framework auf die tatsächliche Datenbank zugreifen, handelt es sich nicht um Komponententests, sondern um Integrationstests, die viel langsamer sind.
Wenn Sie DbContext direkt verwenden, müssen Sie ihn modellieren oder Komponententests ausführen, indem Sie einen SQL Server im Arbeitsspeicher mit vorhersagbaren Daten für Komponententests verwenden. Das Mocken von DbContext oder das Steuern gefälschter Daten erfordert jedoch mehr Arbeit als das Mocken auf der Ebene des Repositorys. Natürlich könnten Sie die MVC-Controller immer testen.
EF-DbContext und IUnitOfWork-Instanzlebensdauer in Ihrem IoC-Container
Das DbContext
Objekt (als IUnitOfWork
Objekt verfügbar gemacht) sollte für mehrere Repositorys innerhalb desselben HTTP-Anforderungsbereichs freigegeben werden. Dies gilt beispielsweise, wenn der ausgeführte Vorgang mehrere Aggregate verarbeiten muss, oder einfach weil Sie mehrere Repositoryinstanzen verwenden. Es ist auch wichtig zu erwähnen, dass die IUnitOfWork
Schnittstelle Teil Ihrer Domänenebene ist, nicht ein EF Core-Typ.
Zu diesem Zweck muss für die Instanz des DbContext
Objekts die Dienstlebensdauer auf ServiceLifetime.Scoped festgelegt sein. Dies ist die Standardlebensdauer beim Registrieren eines DbContext
mit builder.Services.AddDbContext
in Ihrem IoC-Container über die Datei Program.cs in Ihrem ASP.NET Core Web API-Projekt. Der folgende Code veranschaulicht dies.
// Add framework services.
builder.Services.AddMvc(options =>
{
options.Filters.Add(typeof(HttpGlobalExceptionFilter));
}).AddControllersAsServices();
builder.Services.AddEntityFrameworkSqlServer()
.AddDbContext<OrderingContext>(options =>
{
options.UseSqlServer(Configuration["ConnectionString"],
sqlOptions => sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().
Assembly.GetName().Name));
},
ServiceLifetime.Scoped // Note that Scoped is the default choice
// in AddDbContext. It is shown here only for
// pedagogic purposes.
);
Der DbContext-Instanziierungsmodus sollte nicht als ServiceLifetime.Transient oder ServiceLifetime.Singleton konfiguriert werden.
Die Lebensdauer der Repositoryinstanz in Ihrem IoC-Container
Auf ähnliche Weise sollte die Lebensdauer des Repositorys in der Regel als bereichsbezogen (InstancePerLifetimeScope in Autofac) festgelegt werden. Sie könnte auch vorübergehender Natur sein (InstancePerDependency in Autofac), aber Ihr Dienst wird speichereffizienter sein, wenn Sie die bereichsbezogene Lebensdauer verwenden.
// Registering a Repository in Autofac IoC container
builder.RegisterType<OrderRepository>()
.As<IOrderRepository>()
.InstancePerLifetimeScope();
Eine Verwendung der Singleton-Lebensdauer für das Repository kann zu ernsthaften Parallelitätsproblemen führen, wenn DbContext auf eine bereichsbezogene (InstancePerLifetimeScope) Lebensdauer festgelegt ist (die Standardlebensdauer für DbContext). Solange Ihre Dienstlebensdauern für Ihre Repositorys und Ihre DbContext-Instanz beide bereichsbezogenen sind, vermeiden Sie diese Probleme.
Weitere Ressourcen
Implementieren des Repositorys und der Arbeitsmustereinheit in einer ASP.NET MVC-Anwendung
https://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-applicationJonathan Allen. Implementierungsstrategien für das Repositorymuster mit Entity Framework, Dapper und Chain
https://www.infoq.com/articles/repository-implementation-strategiesCesar de la Torre. Vergleich der Lebensdauer: ASP.NET Core-IoC-Containerdienste vs. Autofac-IoC-Containerinstanzbereiche
https://devblogs.microsoft.com/cesardelatorre/comparing-asp-net-core-ioc-service-life-times-and-autofac-ioc-instance-scopes/
Tabellenzuordnung
Die Tabellenzuordnung identifiziert die Tabellendaten, die abgefragt und in der Datenbank gespeichert werden sollen. Zuvor haben Sie gesehen, wie Domänenentitäten (z. B. eine Produkt- oder Auftragsdomäne) zum Generieren eines zugehörigen Datenbankschemas verwendet werden können. EF ist stark auf das Konzept der Konventionen ausgelegt. Konventionen befassen sich mit Fragen wie "Wie lautet der Name einer Tabelle?" oder "Welche Eigenschaft ist der Primärschlüssel?" Konventionen basieren in der Regel auf herkömmlichen Namen. Beispielsweise ist es typisch, dass der Primärschlüssel eine Eigenschaft ist, die mit Id
endet.
Standardmäßig wird jede Entität so eingerichtet, dass sie einer Tabelle zugeordnet wird, die denselben Namen wie die DbSet<TEntity>
-Eigenschaft hat, welche die Entität im abgeleiteten Kontext verfügbar macht. Wenn für die angegebene Entität kein DbSet<TEntity>
Wert angegeben wird, wird der Klassenname verwendet.
Datenanmerkungen im Vergleich zur Fluent-API
Es gibt viele zusätzliche EF Core-Konventionen, und die meisten davon können mithilfe von Datenanmerkungen oder Fluent-API geändert werden, die in der OnModelCreating-Methode implementiert werden.
Datenanmerkungen müssen für die Entitätsmodellklassen selbst verwendet werden, was aus DDD-Sicht aufdringlicher ist. Dies liegt daran, dass Sie Ihr Modell mit Datenanmerkungen im Zusammenhang mit der Infrastrukturdatenbank verunreinigen. Die Fluent-API ist dagegen eine bequeme Möglichkeit, die meisten Konventionen und Zuordnungen innerhalb Ihrer Datenpersistenzinfrastrukturebene zu ändern, sodass das Entitätsmodell sauber und von der Persistenzinfrastruktur entkoppelt wird.
Fluent-API und die OnModelCreating-Methode
Wie bereits erwähnt, können Sie die OnModelCreating-Methode in der DbContext-Klasse verwenden, um Konventionen und Zuordnungen zu ändern.
Der Sortierungs-Microservice in eShopOnContainers implementiert bei Bedarf explizite Zuordnungen und Konfigurationen, wie im folgenden Code gezeigt.
// At OrderingContext.cs from eShopOnContainers
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// ...
modelBuilder.ApplyConfiguration(new OrderEntityTypeConfiguration());
// Other entities' configuration ...
}
// At OrderEntityTypeConfiguration.cs from eShopOnContainers
class OrderEntityTypeConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> orderConfiguration)
{
orderConfiguration.ToTable("orders", OrderingContext.DEFAULT_SCHEMA);
orderConfiguration.HasKey(o => o.Id);
orderConfiguration.Ignore(b => b.DomainEvents);
orderConfiguration.Property(o => o.Id)
.UseHiLo("orderseq", OrderingContext.DEFAULT_SCHEMA);
//Address value object persisted as owned entity type supported since EF Core 2.0
orderConfiguration
.OwnsOne(o => o.Address, a =>
{
a.WithOwner();
});
orderConfiguration
.Property<int?>("_buyerId")
.UsePropertyAccessMode(PropertyAccessMode.Field)
.HasColumnName("BuyerId")
.IsRequired(false);
orderConfiguration
.Property<DateTime>("_orderDate")
.UsePropertyAccessMode(PropertyAccessMode.Field)
.HasColumnName("OrderDate")
.IsRequired();
orderConfiguration
.Property<int>("_orderStatusId")
.UsePropertyAccessMode(PropertyAccessMode.Field)
.HasColumnName("OrderStatusId")
.IsRequired();
orderConfiguration
.Property<int?>("_paymentMethodId")
.UsePropertyAccessMode(PropertyAccessMode.Field)
.HasColumnName("PaymentMethodId")
.IsRequired(false);
orderConfiguration.Property<string>("Description").IsRequired(false);
var navigation = orderConfiguration.Metadata.FindNavigation(nameof(Order.OrderItems));
// DDD Patterns comment:
//Set as field (New since EF 1.1) to access the OrderItem collection property through its field
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
orderConfiguration.HasOne<PaymentMethod>()
.WithMany()
.HasForeignKey("_paymentMethodId")
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
orderConfiguration.HasOne<Buyer>()
.WithMany()
.IsRequired(false)
.HasForeignKey("_buyerId");
orderConfiguration.HasOne(o => o.OrderStatus)
.WithMany()
.HasForeignKey("_orderStatusId");
}
}
Sie können alle Fluent-API-Zuordnungen innerhalb derselben OnModelCreating
Methode festlegen, aber es ist ratsam, diesen Code zu partitionieren und mehrere Konfigurationsklassen zu haben, eine pro Entität, wie im Beispiel gezeigt. Insbesondere für große Modelle ist es ratsam, separate Konfigurationsklassen für die Konfiguration verschiedener Entitätstypen zu haben.
Der Code im Beispiel zeigt einige explizite Deklarationen und Zuordnungen. Ef Core-Konventionen führen jedoch viele dieser Zuordnungen automatisch aus, sodass der tatsächliche Code, den Sie in Ihrem Fall benötigen, möglicherweise kleiner ist.
Der Hi/Lo-Algorithmus in EF Core
Ein interessanter Aspekt des Codes im vorherigen Beispiel ist, dass er den Hi/Lo-Algorithmus als Schlüsselgenerierungsstrategie verwendet.
Der Hi/Lo-Algorithmus ist nützlich, wenn Sie eindeutige Schlüssel benötigen, bevor Sie Änderungen übernehmen. Als Zusammenfassung weist der Hi-Lo-Algorithmus Tabellenzeilen eindeutige Bezeichner zu, ohne dass die Zeile sofort in der Datenbank gespeichert wird. Auf diese Weise können Sie sofort mit der Verwendung der Bezeichner beginnen, wie bei normalen sequenziellen Datenbank-IDs.
Der Hi/Lo-Algorithmus beschreibt einen Mechanismus zum Abrufen eines Batches eindeutiger IDs aus einer verwandten Datenbanksequenz. Diese IDs sind sicher zu verwenden, da die Datenbank die Eindeutigkeit garantiert, sodass keine Konflikte zwischen Benutzern bestehen. Dieser Algorithmus ist aus diesen Gründen interessant:
Das Arbeitseinheitsmuster wird nicht unterbrochen.
Es ruft Sequenz-IDs in Batches ab, um Roundtrips zur Datenbank zu minimieren.
Es generiert einen menschlich lesbaren Bezeichner, im Gegensatz zu Techniken, die GUIDs verwenden.
EF Core unterstützt HiLo mit der UseHiLo
Methode, wie im vorherigen Beispiel gezeigt.
Zuordnen von Feldern anstelle von Eigenschaften
Mit diesem Feature, das seit EF Core 1.1 verfügbar ist, können Sie Spalten direkt Feldern zuordnen. Es ist möglich, keine Eigenschaften in der Entitätsklasse zu verwenden und nur Spalten aus einer Tabelle zu Feldern zuzuordnen. Eine häufige Verwendung dafür wäre private Felder für jeden internen Status, auf den nicht von außerhalb des Objekts zugegriffen werden muss.
Sie können dies mit einzelnen Feldern oder auch mit Sammlungen wie einem List<>
Feld tun. Dieser Punkt wurde bereits erwähnt, als wir die Modellierung der Domänenmodellklassen erörtert haben, aber hier können Sie sehen, wie diese Zuordnung mit der PropertyAccessMode.Field
im vorherigen Code hervorgehobenen Konfiguration ausgeführt wird.
Verwenden Sie Schatteneigenschaften in EF Core, die auf Infrastrukturebene verborgen sind.
Schatteneigenschaften in EF Core sind Eigenschaften, die in Ihrem Entitätsklassenmodell nicht vorhanden sind. Die Werte und Zustände dieser Eigenschaften werden rein in der ChangeTracker-Klasse auf Infrastrukturebene verwaltet.
Implementieren des Abfragespezifikationsmusters
Wie weiter oben im Entwurfsabschnitt eingeführt, ist das Abfragespezifikationsmuster ein Domain-Driven Entwurfsmuster, das als Ort entworfen wurde, an dem Sie die Definition einer Abfrage mit optionaler Sortier- und Paginglogik platzieren können.
Das Abfragespezifikationsmuster definiert eine Abfrage in einem Objekt. Um beispielsweise eine seitenseitige Abfrage zu kapseln, die nach einigen Produkten sucht, können Sie eine PagedProduct-Spezifikation erstellen, die die erforderlichen Eingabeparameter akzeptiert (pageNumber, pageSize, filter usw.). Anschließend würde er innerhalb einer Repositorymethode (in der Regel eine List()-Überladung eine IQuerySpecification akzeptieren und die erwartete Abfrage basierend auf dieser Spezifikation ausführen.
Ein Beispiel für eine generische Spezifikationsschnittstelle ist der folgende Code, der dem code ähnelt, der in der eShopOnWeb-Referenzanwendung verwendet wird.
// GENERIC SPECIFICATION INTERFACE
// https://github.com/dotnet-architecture/eShopOnWeb
public interface ISpecification<T>
{
Expression<Func<T, bool>> Criteria { get; }
List<Expression<Func<T, object>>> Includes { get; }
List<string> IncludeStrings { get; }
}
Anschließend folgt die Implementierung einer generischen Spezifikationsbasisklasse:
// GENERIC SPECIFICATION IMPLEMENTATION (BASE CLASS)
// https://github.com/dotnet-architecture/eShopOnWeb
public abstract class BaseSpecification<T> : ISpecification<T>
{
public BaseSpecification(Expression<Func<T, bool>> criteria)
{
Criteria = criteria;
}
public Expression<Func<T, bool>> Criteria { get; }
public List<Expression<Func<T, object>>> Includes { get; } =
new List<Expression<Func<T, object>>>();
public List<string> IncludeStrings { get; } = new List<string>();
protected virtual void AddInclude(Expression<Func<T, object>> includeExpression)
{
Includes.Add(includeExpression);
}
// string-based includes allow for including children of children
// for example, Basket.Items.Product
protected virtual void AddInclude(string includeString)
{
IncludeStrings.Add(includeString);
}
}
Die folgende Spezifikation lädt eine einzelne Warenkorbentität entweder anhand der ID des Warenkorbs oder der ID des Käufers, zu dem der Warenkorb gehört. Sie lädt die -Sammlung des Warenkorbs Items
.
// SAMPLE QUERY SPECIFICATION IMPLEMENTATION
public class BasketWithItemsSpecification : BaseSpecification<Basket>
{
public BasketWithItemsSpecification(int basketId)
: base(b => b.Id == basketId)
{
AddInclude(b => b.Items);
}
public BasketWithItemsSpecification(string buyerId)
: base(b => b.BuyerId == buyerId)
{
AddInclude(b => b.Items);
}
}
Und schließlich sehen Sie unten, wie ein generisches EF-Repository eine solche Spezifikation verwenden kann, um Daten zu filtern und vorzeitig zu laden, die zu einem bestimmten Entitätstypen T gehören.
// GENERIC EF REPOSITORY WITH SPECIFICATION
// https://github.com/dotnet-architecture/eShopOnWeb
public IEnumerable<T> List(ISpecification<T> spec)
{
// fetch a Queryable that includes all expression-based includes
var queryableResultWithIncludes = spec.Includes
.Aggregate(_dbContext.Set<T>().AsQueryable(),
(current, include) => current.Include(include));
// modify the IQueryable to include any string-based include statements
var secondaryResult = spec.IncludeStrings
.Aggregate(queryableResultWithIncludes,
(current, include) => current.Include(include));
// return the result of the query using the specification's criteria expression
return secondaryResult
.Where(spec.Criteria)
.AsEnumerable();
}
Zusätzlich zur Kapselung der Filterlogik kann die Spezifikation die Struktur der zurückgegebenen Daten angeben, einschließlich der auszufüllenden Eigenschaften.
Obwohl davon abgeraten wird, IQueryable
-Objekte aus einem Repository zurückzugeben, ist es kein Problem, sie innerhalb des Repositorys zum Erstellen eines Resultsets zu verwenden. Sie können diesen Ansatz in der obigen List-Methode sehen, die Zwischenausdrücke IQueryable
verwendet, um die Liste der Eingeschlossenen der Abfrage zu erstellen, bevor Sie die Abfrage mit den Kriterien der Spezifikation in der letzten Zeile ausführen.
Erfahren Sie , wie das Spezifikationsmuster im eShopOnWeb-Beispiel angewendet wird.
Weitere Ressourcen
Tabellen-Zuordnung
https://learn.microsoft.com/ef/core/modeling/relational/tablesVerwenden von HiLo zum Generieren von Schlüsseln mit Entity Framework Core
https://www.talkingdotnet.com/use-hilo-to-generate-keys-with-entity-framework-core/Stützfelder
https://learn.microsoft.com/ef/core/modeling/backing-fieldSteve Smith. Gekapselte Auflistungen in Entity Framework Core
https://ardalis.com/encapsulated-collections-in-entity-framework-coreSchatteneigenschaften
https://learn.microsoft.com/ef/core/modeling/shadow-propertiesDas Spezifikationsmuster
https://deviq.com/specification-pattern/Ardalis.Specification NuGet-Paket Verwendet von eShopOnWeb. \ https://www.nuget.org/packages/Ardalis.Specification