Poskytovatel EF Core Azure Cosmos DBEF Core Azure Cosmos DB Provider

Poznámka

Tento zprostředkovatel je v EF Core 3,0 novinkou.This provider is new in EF Core 3.0.

Tento poskytovatel databáze umožňuje použití Entity Framework Core s Azure Cosmos DB.This database provider allows Entity Framework Core to be used with Azure Cosmos DB. Poskytovatel se udržuje jako součást projektu Entity Framework Core.The provider is maintained as part of the Entity Framework Core Project.

Před čtením této části se důrazně doporučuje seznámení s Azure Cosmos DB dokumentaci .It is strongly recommended to familiarize yourself with the Azure Cosmos DB documentation before reading this section.

Poznámka

Tento zprostředkovatel funguje jenom s rozhraním SQL API Azure Cosmos DB.This provider only works with the SQL API of Azure Cosmos DB.

Instalace produktuInstall

Nainstalujte balíček NuGet Microsoft. EntityFrameworkCore. Cosmos.Install the Microsoft.EntityFrameworkCore.Cosmos NuGet package.

dotnet add package Microsoft.EntityFrameworkCore.Cosmos

ZačínámeGet started

Tip

Ukázku tohoto článku můžete zobrazit na GitHubu.You can view this article's sample on GitHub.

Podobně jako u jiných poskytovatelů je prvním krokem volání UseCosmos: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");

Varování

Koncový bod a klíč se tady pevně zakódované pro jednoduchost, ale v produkční aplikaci by se měly ukládat securily .The endpoint and key are hardcoded here for simplicity, but in a production app these should be stored securily

V tomto příkladu Order je jednoduchá entita s odkazem na vlastní 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; }
}

Ukládání a quering dat se řídí normálním vzorem EF: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();
}

Důležité

Volání EnsureCreatedAsync je nezbytné pro vytvoření požadovaných kontejnerů a vložení počátečních dat , pokud jsou uvedena v modelu.Calling EnsureCreatedAsync is necessary to create the required containers and insert the seed data if present in the model. EnsureCreatedAsync by však měly být volány pouze během nasazení, nikoli v normálním provozu, protože může dojít k problémům s výkonem.However EnsureCreatedAsync should only be called during deployment, not normal operation, as it may cause performance issues.

Přizpůsobení modelu specifického pro CosmosCosmos-specific model customization

Ve výchozím nastavení jsou všechny typy entit namapovány na stejný kontejner pojmenovaný za odvozeným kontextem ("OrderContext" v tomto případě).By default all entity types are mapped to the same container, named after the derived context ("OrderContext" in this case). Chcete-li změnit výchozí název kontejneru, použijte HasDefaultContainer:To change the default container name use HasDefaultContainer:

modelBuilder.HasDefaultContainer("Store");

Pokud chcete mapovat typ entity na jiný kontejner, použijte ToContainer:To map an entity type to a different container use ToContainer:

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

Chcete-li identifikovat typ entity, který daná položka představuje EF Core přidá hodnotu diskriminátoru, i když nejsou k dispozici žádné odvozené typy entit.To identify the entity type that a given item represent EF Core adds a discriminator value even if there are no derived entity types. Název a hodnotu diskriminátoru lze změnit.The name and value of the discriminator can be changed.

Pokud se v jednom kontejneru nebude nikdy ukládat žádný jiný typ entity, může se diskriminátor odebrat voláním HasNoDiscriminator: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();

Klíče oddíluPartition keys

Ve výchozím nastavení EF Core vytvoří kontejnery s klíčem oddílu nastaveným na "__partitionKey" bez zadání jakékoli hodnoty při vkládání položek.By default EF Core will create containers with the partition key set to "__partitionKey" without supplying any value for it when inserting items. Pokud ale chcete plně využívat možnosti výkonu Azure Cosmos, měli byste použít pečlivě vybraný klíč oddílu .But to fully leverage the performance capabilities of Azure Cosmos a carefully selected partition key should be used. Dá se nakonfigurovat voláním HasPartitionKey:It can be configured by calling HasPartitionKey:

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

Poznámka

Vlastnost klíče oddílu může být libovolného typu, pokud je převedena na řetězec.The partition key property can be of any type as long as it is converted to string.

Po nakonfigurování by vlastnost klíče oddílu měla vždycky mít hodnotu, která není null.Once configured the partition key property should always have a non-null value. Při vystavení dotazu je možné přidat podmínku, která bude obsahovat jeden oddíl.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();
}

Vložené entityEmbedded entities

Pro entity vlastněné v Cosmos jsou vložené do stejné položky jako vlastník.For Cosmos owned entities are embedded in the same item as the owner. Chcete-li změnit název vlastnosti, použijte 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");
    });

V této konfiguraci je pořadí z výše uvedeného příkladu uloženo takto: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
}

Jsou vložené i kolekce vlastněných entit.Collections of owned entities are embedded as well. V dalším příkladu použijeme třídu Distributor s kolekcí 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; }
}

Vlastněné entity nemusejí poskytovat explicitní hodnoty klíčů, které se mají uložit: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();
}

Budou trvale zachované tímto způsobem: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ě EF Core vždy musí mít jedinečné hodnoty klíčů pro všechny sledované entity.Internally EF Core always needs to have unique key values for all tracked entities. Primární klíč vytvořený ve výchozím nastavení pro kolekce vlastněných typů se skládá z vlastností cizích klíčů odkazujících na vlastníka a vlastnost int odpovídající indexu v poli JSON.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. Pro načtení těchto hodnot můžete použít rozhraní API pro zápis: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();
}

Tip

V případě potřeby je možné změnit výchozí primární klíč pro vlastní typy entit, ale hodnoty klíčů by měly být poskytnuty explicitně.When necessary the default primary key for the owned entity types can be changed, but then key values should be provided explicitly.

Práce s odpojenými entitamiWorking with disconnected entities

Každá položka musí mít id hodnotu, která je pro daný klíč oddílu jedinečná.Every item needs to have an id value that is unique for the given partition key. Ve výchozím nastavení EF Core generuje hodnotu zřetězením diskriminátoru a hodnot primárního klíče pomocí | jako oddělovače.By default EF Core generates the value by concatenating the discriminator and the primary key values, using '|' as a delimiter. Hodnoty klíče jsou generovány pouze tehdy, když entita vstoupí do stavu Added.The key values are only generated when an entity enters the Added state. To může představovat problém při připojování entit , pokud nemají vlastnost id v typu .NET k uložení hodnoty.This might pose a problem when attaching entities if they don't have an id property on the .NET type to store the value.

Pokud chcete toto omezení obejít, můžete vytvořit a nastavit hodnotu id ručně nebo označit entitu jako přidanou a pak ji změnit na požadovaný stav: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}");
}

Toto je výsledný formát JSON: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
}