EF Core Azure Cosmos DB-AnbieterEF Core Azure Cosmos DB Provider

Hinweis

Dieser Anbieter ist neu in EF Core 3.0.This provider is new in EF Core 3.0.

Dieser Datenbankanbieter ermöglicht die Verwendung von Entity Framework Core mit Azure Cosmos DB.This database provider allows Entity Framework Core to be used with Azure Cosmos DB. Dieser Anbieter wird nicht im Rahmen des Entity Framework Core-Projekts verwaltet.The provider is maintained as part of the Entity Framework Core Project.

Es wird dringend empfohlen, sich vor dem Lesen dieses Abschnitts mit der Azure Cosmos DB-Dokumentation vertraut zu machen.It is strongly recommended to familiarize yourself with the Azure Cosmos DB documentation before reading this section.

Hinweis

Dieser Anbieter kann nur mit der SQL-API von Azure Cosmos DB verwendet werden.This provider only works with the SQL API of Azure Cosmos DB.

InstallierenInstall

Installieren Sie das NuGet-Paket „Microsoft.EntityFrameworkCore.Cosmos“.Install the Microsoft.EntityFrameworkCore.Cosmos NuGet package.

dotnet add package Microsoft.EntityFrameworkCore.Cosmos

Erste SchritteGet started

Tipp

Das in diesem Artikel verwendete Beispiel finden Sie auf GitHub.You can view this article's sample on GitHub.

Wie bei anderen Anbietern besteht der erste Schritt darin, UseCosmos aufzurufen:Like for other providers the first step is to call UseCosmos:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.UseCosmos(
            "https://localhost:8081",
            "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
            databaseName: "OrdersDB");

Warnung

Der Endpunkt und der Schlüssel sind aus Gründen der Einfachheit hier hartcodiert, aber in einer Produktions-App sollten sie sicher gespeichert werden.The endpoint and key are hardcoded here for simplicity, but in a production app these should be stored securely.

In diesem Beispiel ist Order eine einfache Entität mit einem Verweis auf den nicht eigenständigen Typ StreetAddress.In this example Order is a simple entity with a reference to the owned type StreetAddress.

public class Order
{
    public int Id { get; set; }
    public int? TrackingNumber { get; set; }
    public string PartitionKey { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}
public class StreetAddress
{
    public string Street { get; set; }
    public string City { get; set; }
}

Das Speichern und Abfragen von Daten folgt dem normalen EF-Muster:Saving and quering data follows the normal EF pattern:

using (var context = new OrderContext())
{
    await context.Database.EnsureDeletedAsync();
    await context.Database.EnsureCreatedAsync();

    context.Add(new Order
    {
        Id = 1,
        ShippingAddress = new StreetAddress { City = "London", Street = "221 B Baker St" },
        PartitionKey = "1"
    });

    await context.SaveChangesAsync();
}

using (var context = new OrderContext())
{
    var order = await context.Orders.FirstAsync();
    Console.WriteLine($"First order will ship to: {order.ShippingAddress.Street}, {order.ShippingAddress.City}");
    Console.WriteLine();
}

Wichtig

Der Aufruf von EnsureCreatedAsync ist erforderlich, um die erforderlichen Container zu erstellen und die Seeddaten einzufügen, wenn Sie im Modell vorhanden sind.Calling EnsureCreatedAsync is necessary to create the required containers and insert the seed data if present in the model. EnsureCreatedAsync sollte jedoch nur während der Bereitstellung und nicht im normalen Betrieb aufgerufen werden, da dies zu Leistungsproblemen führen kann.However EnsureCreatedAsync should only be called during deployment, not normal operation, as it may cause performance issues.

Cosmos-spezifische ModellanpassungCosmos-specific model customization

Standardmäßig werden alle Entitätstypen demselben Container zugeordnet, der nach dem abgeleiteten Kontext benannt wird (in diesem Fall "OrderContext").By default all entity types are mapped to the same container, named after the derived context ("OrderContext" in this case). Verwenden Sie zum Ändern des Standardcontainernamens HasDefaultContainer:To change the default container name use HasDefaultContainer:

modelBuilder.HasDefaultContainer("Store");

Um einen Entitätstyp einem anderen Container zuzuordnen, verwenden Sie ToContainer:To map an entity type to a different container use ToContainer:

modelBuilder.Entity<Order>()
    .ToContainer("Orders");

Um den Entitätstyp zu identifizieren, den ein bestimmtes Element darstellt, fügt EF Core einen Diskriminatorwert auch dann hinzu, wenn keine abgeleiteten Entitätstypen vorhanden sind.To identify the entity type that a given item represent EF Core adds a discriminator value even if there are no derived entity types. Der Name und der Wert des Diskriminators können geändert werden.The name and value of the discriminator can be changed.

Wenn kein anderer Entitätstyp jemals im gleichen Container gespeichert wird, kann der Diskriminator durch Aufruf von HasNoDiscriminator entfernt werden:If no other entity type will ever be stored in the same container the discriminator can be removed by calling HasNoDiscriminator:

modelBuilder.Entity<Order>()
    .HasNoDiscriminator();

PartitionsschlüsselPartition keys

Standardmäßig erstellt EF Core Container, bei denen der Partitionsschlüssel auf "__partitionKey" festgelegt ist, ohne beim Einfügen von Elementen einen Wert dafür anzugeben.By default EF Core will create containers with the partition key set to "__partitionKey" without supplying any value for it when inserting items. Um die Leistungsfunktionen von Azure Cosmos vollständig nutzen zu können, sollten Sie jedoch einen sorgfältig ausgewählten Partitionsschlüssel verwenden.But to fully leverage the performance capabilities of Azure Cosmos a carefully selected partition key should be used. Dieser kann durch Aufrufen von HasPartitionKey konfiguriert werden:It can be configured by calling HasPartitionKey:

modelBuilder.Entity<Order>()
    .HasPartitionKey(o => o.PartitionKey);

Hinweis

Als Eigenschaft des Partitionsschlüssels kann ein beliebiger Typ verwendet werden, sofern dieser in eine Zeichenfolge konvertiert wird.The partition key property can be of any type as long as it is converted to string.

Nach der Konfiguration muss die Partitionsschlüsseleigenschaft immer einen Wert aufweisen, der nicht NULL ist.Once configured the partition key property should always have a non-null value. Beim Ausgeben einer Abfrage kann eine Bedingung hinzugefügt werden, um eine einzelne Partition zu erstellen.When issuing a query a condition can be added to make it single-partition.

using (var context = new OrderContext())
{
    context.Add(new Order
    {
        Id = 2,
        ShippingAddress = new StreetAddress { City = "New York", Street = "11 Wall Street" },
        PartitionKey = "2"
    });

    await context.SaveChangesAsync();
}

using (var context = new OrderContext())
{
    var order = await context.Orders.Where(p => p.PartitionKey == "2").LastAsync();
    Console.Write("Last order will ship to: ");
    Console.WriteLine($"{order.ShippingAddress.Street}, {order.ShippingAddress.City}");
    Console.WriteLine();
}

Eingebettete EntitätenEmbedded entities

Nicht eigenständige Cosmos-Entitäten sind in das gleiche Element wie der Besitzer eingebettet.For Cosmos owned entities are embedded in the same item as the owner. Verwenden Sie zum Ändern eines Eigenschaftennamens ToJsonProperty:To change a property name use 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:With this configuration the order from the example above is stored like this:

{
    "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.Collections of owned entities are embedded as well. Im nächsten Beispiel verwenden wir die Distributor-Klasse mit einer Sammlung von StreetAddress:For the next example we'll use the Distributor class with a collection of StreetAddress:

public class Distributor
{
    public int Id { 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:The owned entities don't need to provide explicit key values to be stored:

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:They will be persisted in this way:

{
    "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.Internally EF Core always needs to have unique key values for all tracked entities. 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.The primary key created by default for collections of owned types consists of the foreign key properties pointing to the owner and an int property corresponding to the index in the JSON array. Zum Abrufen dieser Werte kann die Eingabe-API verwendet werden:To retrieve these values entry API could be used:

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.When necessary the default primary key for the owned entity types can be changed, but then key values should be provided explicitly.

Arbeiten mit getrennten EntitätenWorking with disconnected entities

Jedes Element muss über einen id-Wert verfügen, der für den angegebenen Partitionsschlüssel eindeutig ist.Every item needs to have an id value that is unique for the given partition key. Standardmäßig generiert EF Core den Wert durch Verkettung der Diskriminatorwerte und der Primärschlüsselwerte, wobei „|“ als Trennzeichen verwendet wird.By default EF Core generates the value by concatenating the discriminator and the primary key values, using '|' as a delimiter. Die Schlüsselwerte werden nur generiert, wenn eine Entität in den Zustand Added wechselt.The key values are only generated when an entity enters the Added state. Dies kann ein Problem darstellen, wenn Sie Entitäten anfügen, wenn diese nicht über eine id-Eigenschaft für den .NET-Typ verfügen, um den Wert zu speichern.This might pose a problem when attaching entities if they don't have an id property on the .NET type to store the value.

Um diese Einschränkung zu umgehen, können Sie den id-Wert manuell erstellen und festlegen oder die Entität zuerst als hinzugefügt markieren und dann in den gewünschten Zustand ändern:To work around this limitation one could create and set the id value manually or mark the entity as added first, then changing it to the desired state:

using (var context = new OrderContext())
{
    var distributorEntry = context.Add(distributor);
    distributorEntry.State = EntityState.Unchanged;

    distributor.ShippingCenters.Remove(distributor.ShippingCenters.Last());

    await context.SaveChangesAsync();
}

using (var context = new OrderContext())
{
    var firstDistributor = await context.Distributors.FirstAsync();
    Console.WriteLine($"Number of shipping centers is now: {firstDistributor.ShippingCenters.Count}");

    var distributorEntry = context.Entry(firstDistributor);
    var idProperty = distributorEntry.Property<string>("id");
    Console.WriteLine($"The distributor 'id' is: {idProperty.CurrentValue}");
}

Dieser JSON-Code ergibt sich:This is the resulting JSON:

{
    "Id": 1,
    "Discriminator": "Distributor",
    "id": "Distributor|1",
    "ShippingCenters": [
        {
            "City": "Phoenix",
            "Street": "500 S 48th Street"
        }
    ],
    "_rid": "JBwtAN8oNYEBAAAAAAAAAA==",
    "_self": "dbs/JBwtAA==/colls/JBwtAN8oNYE=/docs/JBwtAN8oNYEBAAAAAAAAAA==/",
    "_etag": "\"00000000-0000-0000-9377-d7a1ae7c01d5\"",
    "_attachments": "attachments/",
    "_ts": 1572917100
}