Konfigurieren des Modells mit dem EF Core Azure Cosmos DB-Anbieter
Container und Entitätstypen
In Azure Cosmos DB werden JSON-Dokumente in Containern gespeichert. Im Gegensatz zu Tabellen in relationalen Datenbanken können Azure Cosmos DB-Container Dokumente mit unterschiedlichen Formen enthalten. Ein Container erzwingt kein einheitliches Schema für seine Dokumente. Verschiedene Konfigurationsoptionen werden jedoch auf Containerebene definiert und wirken sich daher auf alle darin enthaltenen Dokumente aus. Weitere Informationen finden Sie in der Azure Cosmos DB-Dokumentation zu Containern.
Standardmäßig ordnet EF alle Entitätstypen demselben Container zu; Dies ist in der Regel ein guter Standardwert in Bezug auf Leistung und Preise. Der Standardcontainer wird nach dem .NET-Kontexttyp benannt (OrderContext
in diesem Fall). Verwenden Sie zum Ändern des Standardcontainernamens HasDefaultContainer:
modelBuilder.HasDefaultContainer("Store");
Um einen Entitätstyp einem anderen Container zuzuordnen, verwenden Sie ToContainer:
modelBuilder.Entity<Order>().ToContainer("Orders");
Bevor Sie Entitätstypen verschiedenen Containern zuordnen, stellen Sie sicher, dass Sie die potenziellen Auswirkungen auf Leistung und Preise verstehen (z. B. im Hinblick auf den dedizierten und gemeinsam genutzten Durchsatz). Weitere Informationen finden Sie in der Dokumentation zu Azure Cosmos DB.
IDs und Schlüssel
Azure Cosmos DB erfordert, dass alle Dokumente über eine JSON-Eigenschaft vom Typ id
verfügen, die sie eindeutig identifiziert. Genau wie andere EF-Anbieter sucht auch der EF-Anbieter für Azure Cosmos DB nach einer Eigenschaft namens Id
oder <type name>Id
und versucht, sie als Schlüssel Ihres Entitätstyps zu konfigurieren, um sie der JSON-Eigenschaft id
zuzuordnen. Sie können jede Eigenschaft als Schlüsseleigenschaft konfigurieren, indem Sie HasKey nutzen. Weitere Informationen finden Sie in der allgemeinen EF-Dokumentation zu Schlüsseln.
Entwickler, die von anderen Datenbanken zu Azure Cosmos DB kommen, erwarten manchmal, dass die Schlüsseleigenschaft (Id
) automatisch generiert wird. In SQL Server konfiguriert EF beispielsweise numerische Schlüsseleigenschaften als IDENTITÄTsspalten, wobei automatische Inkrementierungswerte in der Datenbank generiert werden. Im Gegensatz dazu unterstützt Azure Cosmos DB die automatische Generierung von Eigenschaften nicht, daher müssen Schlüsseleigenschaften explizit festgelegt werden. Wenn Sie einen Entitätstyp mit einer nicht festgelegten Schlüsseleigenschaft einfügen, wird einfach der CLR-Standardwert für diese Eigenschaft eingefügt (z. B. 0 für int
), und ein zweites Einfügen schlägt fehl; EF gibt eine Warnung aus, wenn Sie versuchen, dies zu tun.
Wenn Sie über eine GUID als Schlüsseleigenschaft verfügen möchten, können Sie EF so konfigurieren, dass eindeutige Zufallswerte auf dem Client generiert werden:
modelBuilder.Entity<Session>().Property(b => b.Id).HasValueGenerator<GuidValueGenerator>();
Partitionsschlüssel
Azure Cosmos DB verwendet Partitionierung, um eine horizontale Skalierung zu erzielen; eine ordnungsgemäße Modellierung und sorgfältige Auswahl des Partitionsschlüssels ist entscheidend, um eine gute Leistung zu erzielen und Kosten zu senken. Es wird dringend empfohlen, die Azure Cosmos DB-Dokumentation zur Partitionierung zu lesen und Ihre Partitionierungsstrategie im Voraus zu planen.
Um den Partitionsschlüssel mit EF zu konfigurieren, rufen Sie HasPartitionKey auf, und übergeben Sie eine reguläre Eigenschaft für Ihren Entitätstyp:
modelBuilder.Entity<Order>().HasPartitionKey(o => o.PartitionKey);
Jede Eigenschaft kann zu einem Partitionsschlüssel gemacht werden, solange sie in eine Zeichenfolge konvertiert wird. Nach der Konfiguration sollte die Partitionsschlüsseleigenschaft immer einen Wert ungleich Null aufweisen; Wenn Sie versuchen, einen neuen Entitätstyp mit einer nicht festgelegten Partitionsschlüsseleigenschaft einzufügen, tritt ein Fehler auf.
Beachten Sie, dass bei Azure Cosmos DB zwei Dokumente mit der gleichen id
-Eigenschaft in einem Container vorhanden sein können, solange sie sich in unterschiedlichen Partitionen befinden. Das bedeutet, dass sowohl die id
-Eigenschaft als auch die Partitionsschlüsseleigenschaft bereitgestellt werden müssen, um ein Dokument innerhalb eines Containers eindeutig zu identifizieren. Aus diesem Grund enthält EF interne Vorstellung des Entitäts-Primärschlüssels beide Elemente nach Konvention, im Gegensatz zu relationalen Datenbanken, bei denen kein Partitionsschlüsselkonzept vorhanden ist. Dies bedeutet z. B., dass FindAsync
sowohl Schlüssel- als auch Partitionsschlüsseleigenschaften erforderlich sind (siehe weitere Dokumente), und eine Abfrage muss diese in ihrer Where
Klausel angeben, um von effizienten und kostengünstigen point reads
Vorteilen zu profitieren.
Beachten Sie, dass der Partitionsschlüssel auf Containerebene definiert ist. Das bedeutet insbesondere, dass mehrere Entitätstypen im gleichen Container nicht über unterschiedliche Partitionsschlüsseleigenschaften verfügen können. Wenn Sie unterschiedliche Partitionsschlüssel definieren müssen, ordnen Sie die relevanten Entitätstypen verschiedenen Containern zu.
Hierarchische Partitionsschlüssel
Azure Cosmos DB unterstützt auch hierarchische Partitionsschlüssel, um die Datenverteilung noch weiter zu optimieren. Ausführlichere Informationen finden Sie in der Dokumentation. EF 9.0 hat Unterstützung für hierarchische Partitionsschlüssel hinzugefügt; Um diese zu konfigurieren, übergeben Sie einfach bis zu 3 Eigenschaften an HasPartitionKey:
modelBuilder.Entity<Order>().HasPartitionKey(o => new { e.TenantId, e.UserId, e.SessionId });
Mit einem solchen hierarchischen Partitionsschlüssel können Abfragen einfach nur an eine relevante Teilmenge von Teilpartitionen gesendet werden. Wenn Sie z. B. die Bestellungen eines bestimmten Mandanten abfragen, werden diese Abfragen nur für die Unterpartitionen für diesen Mandanten ausgeführt.
Wenn Sie keinen Partitionsschlüssel mit EF konfigurieren, wird beim Start eine Warnung protokolliert. EF Core erstellt Container, auf die der Partitionsschlüssel festgelegt __partitionKey
ist, und gibt beim Einfügen von Elementen keinen Wert dafür an. Ohne festgelegten Partitionsschlüssel wird die Datenkapazität Ihres Containers auf 20 GB beschränkt, was dem maximalen Speicher für eine einzelne logische Partition entspricht. Dies kann zwar für kleine Entwicklungs-/Testanwendungen funktionieren, es wird jedoch dringend davon abgeraten, eine Produktionsanwendung ohne eine angemessen konfigurierte Partitionsschlüsselstrategie bereitzustellen.
Nachdem die Partitionsschlüsseleigenschaften ordnungsgemäß konfiguriert wurden, können Sie Werte für sie in Abfragen bereitstellen. Weitere Informationen finden Sie unter Abfragen mit Partitionsschlüsseln .
Diskriminatoren
Da mehrere Entitätstypen demselben Container zugeordnet werden können, fügt EF Core immer eine $type
Diskriminatoreigenschaft zu allen JSON-Dokumenten hinzu, die Sie speichern (diese Eigenschaft wurde Discriminator
vor EF 9.0 genannt). Dadurch kann EF Dokumente erkennen, die aus der Datenbank geladen werden, und den richtigen .NET-Typ materialisieren. Entwickler, die von relationalen Datenbanken kommen, sind möglicherweise mit Diskriminatoren im Kontext der Vererbung pro Hierarchie (TPH) vertraut. In Azure Cosmos DB werden Diskriminatoren nicht nur in Vererbungszuordnungsszenarien verwendet, sondern auch, weil ein Container völlig unterschiedliche Dokumenttypen enthalten kann.
Der Name und die Werte der Diskriminatoreigenschaft können mit den standardmäßigen EF-APIs konfiguriert werden. Weitere Informationen finden Sie in diesen Dokumenten. Wenn Sie einen einzelnen Entitätstyp einem Container zuordnen, sind Sie sicher, dass Sie nie eine andere zuordnen und die Diskriminatoreigenschaft entfernen möchten, rufen Sie HasNoDiscriminator auf:
modelBuilder.Entity<Order>().HasNoDiscriminator();
Da ein Container unterschiedliche Entitätstypen enthalten kann und die JSON-Eigenschaft id
innerhalb einer Containerpartition eindeutig sein muss, können Sie für Entitäten unterschiedlicher Typen in der gleichen Containerpartition nicht den gleichen id
-Wert haben. Vergleichen Sie dies mit relationalen Datenbanken, wobei jeder Entitätstyp einer anderen Tabelle zugeordnet ist und daher einen eigenen, separaten Schlüsselbereich aufweist. Es liegt daher in Ihrer Verantwortung, die Eindeutigkeit von Dokumenten, die id
Sie in einen Container einfügen, sicherzustellen. Wenn Sie unterschiedliche Entitätstypen mit denselben Primärschlüsselwerten haben müssen, können Sie EF anweisen, den Diskriminator automatisch wie folgt in die id
Eigenschaft einzufügen:
modelBuilder.Entity<Session>().HasDiscriminatorInJsonId();
Dies kann zwar die Arbeit mit id
Werten erleichtern, aber es kann schwieriger sein, mit externen Anwendungen zu arbeiten, die mit Ihren Dokumenten arbeiten, da sie jetzt das verkettete id
Format von EF sowie die Diskriminatorwerte kennen müssen, die standardmäßig von Ihren .NET-Typen abgeleitet sind. Beachten Sie, dass dies das Standardverhalten vor EF 9.0 war.
Eine zusätzliche Option besteht darin, EF anzuweisen, nur den Stammdiskriminator einzufügen, bei dem es sich um den Diskriminator des Stammentitätstyps der Hierarchie handelt, in die id
Eigenschaft:
modelBuilder.Entity<Session>().HasRootDiscriminatorInJsonId();
Dies ist ähnlich, aber EF kann effiziente Punktlesevorgänge in weiteren Szenarien verwenden. Wenn Sie einen Diskriminator in die id
Eigenschaft einfügen müssen, erwägen Sie, den Stammdiskriminator für eine bessere Leistung einzufügen.
Bereitgestellter Durchsatz
Wenn Sie EF Core zum Erstellen der Azure Cosmos DB-Instanz oder von -Containern verwenden, können Sie einen bereitgestellten Durchsatz für die Datenbank durch Aufrufen von CosmosModelBuilderExtensions.HasAutoscaleThroughput oder CosmosModelBuilderExtensions.HasManualThroughput konfigurieren. Zum Beispiel:
modelBuilder.HasManualThroughput(2000);
modelBuilder.HasAutoscaleThroughput(4000);
Um den bereitgestellten Durchsatz für einen Container zu konfigurieren, rufen Sie CosmosEntityTypeBuilderExtensions.HasAutoscaleThroughput oder CosmosEntityTypeBuilderExtensions.HasManualThroughput auf. Zum Beispiel:
modelBuilder.Entity<Family>(
entityTypeBuilder =>
{
entityTypeBuilder.HasManualThroughput(5000);
entityTypeBuilder.HasAutoscaleThroughput(3000);
});
Gültigkeitsdauer
Entitätstypen im Azure Cosmos DB-Modell können mit einer standardmäßigen Gültigkeitsdauer konfiguriert werden. Zum Beispiel:
modelBuilder.Entity<Hamlet>().HasDefaultTimeToLive(3600);
Oder für den analytischen Speicher:
modelBuilder.Entity<Hamlet>().HasAnalyticalStoreTimeToLive(3600);
Die Gültigkeitsdauer für einzelne Entitäten kann mithilfe einer Eigenschaft festgelegt werden, die „ttl“ im JSON-Dokument zugeordnet ist. Zum Beispiel:
modelBuilder.Entity<Village>()
.HasDefaultTimeToLive(3600)
.Property(e => e.TimeToLive)
.ToJsonProperty("ttl");
Hinweis
Für den Entitätstyp muss eine Standardgültigkeitsdauer konfiguriert sein, damit „ttl“ einen Effekt hat. Weitere Informationen finden Sie unter Gültigkeitsdauer (TTL) in Azure Cosmos DB.
Die Eigenschaft „Gültigkeitsdauer“ wird festgelegt, bevor die Entität gespeichert wird. Zum Beispiel:
var village = new Village { Id = "DN41", Name = "Healing", TimeToLive = 60 };
context.Add(village);
await context.SaveChangesAsync();
Die Eigenschaft „Gültigkeitsdauer“ kann eine Schatteneigenschaft sein, um zu vermeiden, dass die Domänenentität mit Überlegungen zur Datenbank belastet wird. Zum Beispiel:
modelBuilder.Entity<Hamlet>()
.HasDefaultTimeToLive(3600)
.Property<int>("TimeToLive")
.ToJsonProperty("ttl");
Die Schatteneigenschaft „Gültigkeitsdauer“ wird dann durch Zugreifen auf die nachverfolgte Entität festgelegt. Zum Beispiel:
var hamlet = new Hamlet { Id = "DN37", Name = "Irby" };
context.Add(hamlet);
context.Entry(hamlet).Property("TimeToLive").CurrentValue = 60;
await context.SaveChangesAsync();
Eingebettete Entitäten
Hinweis
Verknüpfte Entitätstypen sind standardmäßig als nicht eigenständig konfiguriert. Rufen Sie ModelBuilder.Entity auf, um diese für einen bestimmten Entitätstypen zu verhindern.
Nicht eigenständige Azure Cosmos DB-Entitäten sind in das gleiche Element wie der Besitzer eingebettet. Verwenden Sie zum Ändern eines Eigenschaftennamens ToJsonProperty:
modelBuilder.Entity<Order>().OwnsOne(
o => o.ShippingAddress,
sa =>
{
sa.ToJsonProperty("Address");
sa.Property(p => p.Street).ToJsonProperty("ShipsToStreet");
sa.Property(p => p.City).ToJsonProperty("ShipsToCity");
});
Mit dieser Konfiguration wird die Reihenfolge des obigen Beispiels wie folgt gespeichert:
{
"Id": 1,
"PartitionKey": "1",
"TrackingNumber": null,
"id": "1",
"Address": {
"ShipsToCity": "London",
"ShipsToStreet": "221 B Baker St"
},
"_rid": "6QEKAM+BOOABAAAAAAAAAA==",
"_self": "dbs/6QEKAA==/colls/6QEKAM+BOOA=/docs/6QEKAM+BOOABAAAAAAAAAA==/",
"_etag": "\"00000000-0000-0000-683c-692e763901d5\"",
"_attachments": "attachments/",
"_ts": 1568163674
}
Sammlungen von nicht eigenständigen Entitäten werden ebenfalls eingebettet. Im nächsten Beispiel verwenden wir die Distributor
-Klasse mit einer Sammlung von StreetAddress
:
public class Distributor
{
public int Id { get; set; }
public string ETag { get; set; }
public ICollection<StreetAddress> ShippingCenters { get; set; }
}
Die nicht eigenständigen Entitäten müssen keine expliziten Schlüsselwerte angeben, die gespeichert werden sollen:
var distributor = new Distributor
{
Id = 1,
ShippingCenters = new HashSet<StreetAddress>
{
new StreetAddress { City = "Phoenix", Street = "500 S 48th Street" },
new StreetAddress { City = "Anaheim", Street = "5650 Dolly Ave" }
}
};
using (var context = new OrderContext())
{
context.Add(distributor);
await context.SaveChangesAsync();
}
Sie werden auf diese Weise persistent gespeichert:
{
"Id": 1,
"Discriminator": "Distributor",
"id": "Distributor|1",
"ShippingCenters": [
{
"City": "Phoenix",
"Street": "500 S 48th Street"
},
{
"City": "Anaheim",
"Street": "5650 Dolly Ave"
}
],
"_rid": "6QEKANzISj0BAAAAAAAAAA==",
"_self": "dbs/6QEKAA==/colls/6QEKANzISj0=/docs/6QEKANzISj0BAAAAAAAAAA==/",
"_etag": "\"00000000-0000-0000-683c-7b2b439701d5\"",
"_attachments": "attachments/",
"_ts": 1568163705
}
Intern muss EF Core für alle nachverfolgten Entitäten immer eindeutige Schlüsselwerte aufweisen. Der Primärschlüssel, der standardmäßig für Sammlungen nicht eigenständiger Typen erstellt wird, besteht aus den Fremdschlüsseleigenschaften, die auf den Besitzer verweisen, und einer int
-Eigenschaft, die dem Index im JSON-Array entspricht. Zum Abrufen dieser Werte kann die Eingabe-API verwendet werden:
using (var context = new OrderContext())
{
var firstDistributor = await context.Distributors.FirstAsync();
Console.WriteLine($"Number of shipping centers: {firstDistributor.ShippingCenters.Count}");
var addressEntry = context.Entry(firstDistributor.ShippingCenters.First());
var addressPKProperties = addressEntry.Metadata.FindPrimaryKey().Properties;
Console.WriteLine(
$"First shipping center PK: ({addressEntry.Property(addressPKProperties[0].Name).CurrentValue}, {addressEntry.Property(addressPKProperties[1].Name).CurrentValue})");
Console.WriteLine();
}
Tipp
Bei Bedarf kann der standardmäßige Primärschlüssel für die nicht eigenständigen Entitätstypen geändert werden. Dann sollten jedoch Schlüsselwerte explizit angegeben werden.
Eine Sammlung primitiver Typen
Sammlungen unterstützter primitiver Typen, z. B. string
und int
, werden automatisch ermittelt und zugeordnet. Unterstützte Sammlungen sind alle Typen, die IReadOnlyList<T> oder IReadOnlyDictionary<TKey,TValue> implementieren. Betrachten Sie beispielsweise die folgenden Entitätstypen:
public class Book
{
public Guid Id { get; set; }
public string Title { get; set; }
public IList<string> Quotes { get; set; }
public IDictionary<string, string> Notes { get; set; }
}
Die IList
und die IDictionary
kann aufgefüllt und in der Datenbank beibehalten werden:
using var context = new BooksContext();
var book = new Book
{
Title = "How It Works: Incredible History",
Quotes = new List<string>
{
"Thomas (Tommy) Flowers was the British engineer behind the design of the Colossus computer.",
"Invented originally for Guinness, plastic widgets are nitrogen-filled spheres.",
"For 20 years after its introduction in 1979, the Walkman dominated the personal stereo market."
},
Notes = new Dictionary<string, string>
{
{ "121", "Fridges" },
{ "144", "Peter Higgs" },
{ "48", "Saint Mark's Basilica" },
{ "36", "The Terracotta Army" }
}
};
context.Add(book);
await context.SaveChangesAsync();
Dies ergibt folgendes JSON-Dokument:
{
"Id": "0b32283e-22a8-4103-bb4f-6052604868bd",
"Discriminator": "Book",
"Notes": {
"36": "The Terracotta Army",
"48": "Saint Mark's Basilica",
"121": "Fridges",
"144": "Peter Higgs"
},
"Quotes": [
"Thomas (Tommy) Flowers was the British engineer behind the design of the Colossus computer.",
"Invented originally for Guinness, plastic widgets are nitrogen-filled spheres.",
"For 20 years after its introduction in 1979, the Walkman dominated the personal stereo market."
],
"Title": "How It Works: Incredible History",
"id": "Book|0b32283e-22a8-4103-bb4f-6052604868bd",
"_rid": "t-E3AIxaencBAAAAAAAAAA==",
"_self": "dbs/t-E3AA==/colls/t-E3AIxaenc=/docs/t-E3AIxaencBAAAAAAAAAA==/",
"_etag": "\"00000000-0000-0000-9b50-fc769dc901d7\"",
"_attachments": "attachments/",
"_ts": 1630075016
}
Diese Sammlungen können dann wieder auf die übliche Weise aktualisiert werden:
book.Quotes.Add("Pressing the emergency button lowered the rods again.");
book.Notes["48"] = "Chiesa d'Oro";
await context.SaveChangesAsync();
Einschränkungen:
- Nur Wörterbücher mit Zeichenfolgenschlüsseln werden unterstützt.
- Unterstützung für Abfragen in primitive Auflistungen wurde in EF Core 9.0 hinzugefügt.
Optimistische Nebenläufigkeit mit ETags
Rufen Sie UseETagConcurrency auf, um einen Entitätstyp so zu konfigurieren, dass die optimistische Nebenläufigkeit verwendet wird. Dieser Aufruf erstellt eine _etag
-Eigenschaft als Schatteneigenschaft, und diese Eigenschaft wird als Nebenläufigkeitstoken festgelegt.
modelBuilder.Entity<Order>()
.UseETagConcurrency();
Wenn Sie das Auflösen von Nebenläufigkeitsfehlern vereinfachen möchten, können Sie das ETag mithilfe von IsETagConcurrency einer CLR-Eigenschaft zuordnen.
modelBuilder.Entity<Distributor>()
.Property(d => d.ETag)
.IsETagConcurrency();