Sdílet prostřednictvím


Konfigurace modelu pomocí zprostředkovatele Azure Cosmos DB EF Core

Kontejnery a typy entit

Ve službě Azure Cosmos DB jsou dokumenty JSON uložené v kontejnerech. Na rozdíl od tabulek v relačních databázích můžou kontejnery Azure Cosmos DB obsahovat dokumenty s různými obrazci – kontejner neukládá jednotné schéma svých dokumentů. Různé možnosti konfigurace jsou však definovány na úrovni kontejneru, a proto ovlivňují všechny dokumenty obsažené v něm. Další informace najdete v dokumentaci ke službě Azure Cosmos DB v kontejnerech .

Ef ve výchozím nastavení mapuje všechny typy entit do stejného kontejneru; to je obvykle dobrý výchozí způsob z hlediska výkonu a cen. Výchozí kontejner je pojmenován po typu kontextu .NET (OrderContext v tomto případě). Pokud chcete změnit výchozí název kontejneru, použijte HasDefaultContainer:

modelBuilder.HasDefaultContainer("Store");

Mapování typu entity na jiný kontejner:ToContainer

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

Před mapováním typů entit na různé kontejnery se ujistěte, že rozumíte potenciálním dopadům na výkon a ceny (např. pokud jde o vyhrazenou a sdílenou propustnost); Další informace najdete v dokumentaci ke službě Azure Cosmos DB.

ID a klíče

Azure Cosmos DB vyžaduje, aby všechny dokumenty měly id vlastnost JSON, která je jednoznačně identifikuje. Stejně jako ostatní poskytovatelé EF se zprostředkovatel EF Azure Cosmos DB pokusí najít vlastnost s názvem Id nebo <type name>Ida nakonfigurovat tuto vlastnost jako klíč vašeho typu entity, namapuje ji na id vlastnost JSON. Libovolnou vlastnost můžete nakonfigurovat tak, aby byla klíčovou vlastností. HasKeyDalší informace najdete v obecné dokumentaci k ef.

Vývojáři přicházející do služby Azure Cosmos DB z jiných databází někdy očekávají, že se vlastnost klíče (Id) vygeneruje automaticky. Ef například na SQL Serveru konfiguruje číselné vlastnosti klíče tak, aby byly sloupce IDENTITY, kde se v databázi generují hodnoty automatického přírůstku. Azure Cosmos DB naproti tomu nepodporuje automatické generování vlastností, takže je nutné explicitně nastavit klíčové vlastnosti. Vložení typu entity s vlastností klíče bez nastavení jednoduše vloží výchozí hodnotu CLR pro danou vlastnost (např. 0 pro int) a druhé vložení selže; Ef zobrazí upozornění, pokud se to pokusíte provést.

Pokud chcete jako vlastnost klíče mít identifikátor GUID, můžete ef nakonfigurovat tak, aby v klientovi vygeneroval jedinečné náhodné hodnoty:

modelBuilder.Entity<Session>().Property(b => b.Id).HasValueGenerator<GuidValueGenerator>();

Klíče oddílu

Azure Cosmos DB používá dělení k dosažení horizontálního škálování; správné modelování a pečlivé výběru klíče oddílu je nezbytné pro dosažení dobrého výkonu a udržování nákladů na nižší úrovni. Důrazně doporučujeme přečíst si dokumentaci ke službě Azure Cosmos DB týkající se dělení a naplánovat strategii dělení předem.

Pokud chcete nakonfigurovat klíč oddílu pomocí EF, zavolejte HasPartitionKey a předejte jí běžnou vlastnost vašeho typu entity:

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

Libovolnou vlastnost lze vytvořit do klíče oddílu, pokud je převedena na řetězec. Po nakonfigurování by vlastnost klíče oddílu měla mít vždy hodnotu, která nemá hodnotu null; při pokusu o vložení nového typu entity s vlastností klíče oddílu bez nastavení dojde k chybě.

Služba Azure Cosmos DB umožňuje, aby v kontejneru existovaly dva dokumenty se stejnou id vlastností, pokud jsou v různých oddílech. To znamená, že aby bylo možné jednoznačně identifikovat dokument v rámci kontejneru, id musí být k dispozici vlastnosti klíče oddílu i vlastnosti klíče oddílu. Z tohoto důvodu ef interní pojem primárního klíče entity obsahuje oba tyto prvky podle konvence, na rozdíl od relačních databází, kde neexistuje žádný koncept klíče oddílu. To znamená například, že FindAsync vyžaduje vlastnosti klíče i klíče oddílu (viz další dokumenty) a dotaz musí tyto vlastnosti zadat v klauzuliWhere, aby bylo možné využívat efektivní a nákladově efektivní point reads.

Všimněte si, že klíč oddílu je definovaný na úrovni kontejneru. To zejména znamená, že u více typů entit ve stejném kontejneru není možné mít různé vlastnosti klíče oddílu. Pokud potřebujete definovat různé klíče oddílů, namapujte relevantní typy entit na různé kontejnery.

Hierarchické klíče oddílů

Azure Cosmos DB také podporuje hierarchické klíče oddílů pro další optimalizaci distribuce dat; Další podrobnosti najdete v dokumentaci. Ef 9.0 přidal podporu pro hierarchické klíče oddílu; pokud chcete tyto vlastnosti nakonfigurovat, stačí předat až 3 vlastnosti HasPartitionKey:

modelBuilder.Entity<Order>().HasPartitionKey(o => new { e.TenantId, e.UserId, e.SessionId });

Díky takovému hierarchickému klíči oddílu se dotazy dají snadno odesílat jenom do příslušné podmnožina podsadek. Pokud například zadáte dotaz na objednávky konkrétního tenanta, tyto dotazy se budou spouštět pouze na dílčích oddílech daného tenanta.

Pokud klíč oddílu nenakonfigurujete s EF, při spuštění se zaprotokoluje upozornění. EF Core vytvoří kontejnery s klíčem oddílu nastaveným na __partitionKeya při vkládání položek pro něj nezadá žádnou hodnotu. Pokud není nastavený žádný klíč oddílu, kontejner bude omezen na 20 GB dat, což je maximální úložiště pro jeden logický oddíl. I když to může fungovat pro malé vývojové a testovací aplikace, důrazně nedoporučujeme nasadit produkční aplikaci bez dobře nakonfigurované strategie klíče oddílu.

Jakmile jsou vlastnosti klíče oddílu správně nakonfigurované, můžete pro ně zadat hodnoty v dotazech; Další informace najdete v tématu Dotazování pomocí klíčů oddílů.

Diskriminátor

Vzhledem k tomu, že ke stejnému kontejneru může být mapováno více typů entit, EF Core vždy přidá $type diskriminující vlastnost do všech dokumentů JSON, které uložíte (tato vlastnost byla volána Discriminator před EF 9.0), umožňuje EF rozpoznat dokumenty načtené z databáze a materializovat správný typ .NET. Vývojáři pocházející z relačních databází můžou být obeznámeni s diskriminací v kontextu dědičnosti tabulek na hierarchii (TPH). Ve službě Azure Cosmos DB se diskriminují nejen ve scénářích mapování dědičnosti, ale také proto, že stejný kontejner může obsahovat zcela různé typy dokumentů.

Pro standardní rozhraní EF API je možné nakonfigurovat diskriminující název vlastnosti a hodnoty. Další informace najdete v těchto dokumentech. Pokud mapujete jeden typ entity na kontejner, máte jistotu, že nikdy nebudete namapovat jiný typ a chcete se zbavit nediskriminační vlastnosti, zavolejte HasNo Discriminatoror:

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

Vzhledem k tomu, že stejný kontejner může obsahovat různé typy entit a vlastnost JSON id musí být jedinečná v rámci oddílu kontejneru, nemůžete mít stejnou id hodnotu pro entity různých typů ve stejném oddílu kontejneru. Porovnejte to s relačními databázemi, kde je každý typ entity namapován na jinou tabulku, a proto má vlastní samostatný prostor klíče. Je proto vaší zodpovědností zajistit id jedinečnost dokumentů, které vložíte do kontejneru. Pokud potřebujete mít různé typy entit se stejnými hodnotami primárního klíče, můžete ef dát ef pokyn, aby automaticky vložil diskriminátor do id vlastnosti následujícím způsobem:

modelBuilder.Entity<Session>().HasDiscriminatorInJsonId();

I když to může usnadnit práci s id hodnotami, může být obtížnější spolupracovat s externími aplikacemi, které pracují s vašimi dokumenty, protože teď musí vědět o zřetězení id formátu EF a také o nediskriminačních hodnotách, které jsou ve výchozím nastavení odvozené z vašich typů .NET. Všimněte si, že toto bylo výchozí chování před EF 9.0.

Další možností je instruovat EF, aby do vlastnosti vložil pouze kořenový diskriminátor, což je diskriminátor typu kořenové entity hierarchie id :

modelBuilder.Entity<Session>().HasRootDiscriminatorInJsonId();

Je to podobné, ale ef umožňuje používat efektivní čtení bodů ve více scénářích. Pokud potřebujete do vlastnosti vložit diskriminátor, zvažte vložení kořenového diskriminátoru id za účelem lepšího výkonu.

Zřízená propustnost

Pokud k vytvoření databáze nebo kontejnerů Azure Cosmos DB použijete EF Core, můžete nakonfigurovat zřízenou propustnost pro databázi voláním CosmosModelBuilderExtensions.HasAutoscaleThroughput nebo CosmosModelBuilderExtensions.HasManualThroughput. Příklad:

modelBuilder.HasManualThroughput(2000);
modelBuilder.HasAutoscaleThroughput(4000);

Pokud chcete pro kontejner nakonfigurovat zřízenou propustnost, volejte CosmosEntityTypeBuilderExtensions.HasAutoscaleThroughput nebo CosmosEntityTypeBuilderExtensions.HasManualThroughput. Příklad:

modelBuilder.Entity<Family>(
    entityTypeBuilder =>
    {
        entityTypeBuilder.HasManualThroughput(5000);
        entityTypeBuilder.HasAutoscaleThroughput(3000);
    });

Time-to-live

Typy entit v modelu Azure Cosmos DB je možné nakonfigurovat s výchozím časem naživo. Příklad:

modelBuilder.Entity<Hamlet>().HasDefaultTimeToLive(3600);

Nebo pro analytické úložiště:

modelBuilder.Entity<Hamlet>().HasAnalyticalStoreTimeToLive(3600);

Pro jednotlivé entity je možné nastavit časový limit pro jednotlivé entity pomocí vlastnosti mapované na hodnotu ttl v dokumentu JSON. Příklad:

modelBuilder.Entity<Village>()
    .HasDefaultTimeToLive(3600)
    .Property(e => e.TimeToLive)
    .ToJsonProperty("ttl");

Poznámka:

Výchozí hodnota time-to-live musí být nakonfigurovaná pro typ entity, aby "ttl" měla jakýkoli vliv. Další informace najdete v tématu TTL (Time to Live) ve službě Azure Cosmos DB .

Vlastnost time-to-live je pak nastavena před uložením entity. Příklad:

var village = new Village { Id = "DN41", Name = "Healing", TimeToLive = 60 };
context.Add(village);
await context.SaveChangesAsync();

Vlastnost time-to-live může být stínová vlastnost , aby se zabránilo znečisťování entity domény s obavami databáze. Příklad:

modelBuilder.Entity<Hamlet>()
    .HasDefaultTimeToLive(3600)
    .Property<int>("TimeToLive")
    .ToJsonProperty("ttl");

Vlastnost stínového času naživo je pak nastavena přístupem ke sledované entitě. Příklad:

var hamlet = new Hamlet { Id = "DN37", Name = "Irby" };
context.Add(hamlet);
context.Entry(hamlet).Property("TimeToLive").CurrentValue = 60;
await context.SaveChangesAsync();

Vložené entity

Poznámka:

Související typy entit jsou ve výchozím nastavení nakonfigurované jako vlastněné. Voláním ModelBuilder.Entity tomu můžete u konkrétního typu entity zabránit.

Ve službě Azure Cosmos DB jsou vlastněné entity vložené do stejné položky jako vlastník. Ke změně názvu vlastnosti použijte 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");
    });

Při této konfiguraci se objednávka z předchozího příkladu uloží takto:

{
    "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
}

Kolekce vlastněných entit jsou také vložené. V dalším příkladu použijeme třídu Distributor s kolekcí StreetAddress:

public class Distributor
{
    public int Id { get; set; }
    public string ETag { get; set; }
    public ICollection<StreetAddress> ShippingCenters { get; set; }
}

Vlastněné entity nemusí poskytovat explicitní hodnoty klíče, které se mají uložit:

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 zachovány takto:

{
    "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
}

EF Core musí mít interně pro všechny sledované entity vždy jedinečné klíčové hodnoty. Primární klíč standardně vytvořený pro kolekce vlastněných typů sestává z vlastností cizího klíče odkazujících na vlastníka a z vlastnosti int odpovídající indexu v poli JSON. K načtení položky s těmito hodnotami lze použít rozhraní API:

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 lze výchozí primární klíč pro typy vlastněných entit změnit, ale hodnoty klíčů by měly být zadány explicitně.

Kolekce primitivních typů

Kolekce podporovaných primitivních typů, například string a int, jsou zjišťovány a mapovány automaticky. Podporované kolekce jsou všechny typy, které implementují IReadOnlyList<T> nebo IReadOnlyDictionary<TKey,TValue>. Představte si například tento typ entity:

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; }
}

IDictionary Databázi IList je možné naplnit a zachovat:

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();

Výsledkem je následující dokument JSON:

{
    "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
}

Tyto kolekce je pak možné aktualizovat, a to opět běžným způsobem:

book.Quotes.Add("Pressing the emergency button lowered the rods again.");
book.Notes["48"] = "Chiesa d'Oro";

await context.SaveChangesAsync();

Omezení:

  • Podporují se jenom slovníky s řetězcovými klíči.
  • Podpora dotazování na primitivní kolekce byla přidána v EF Core 9.0.

Optimistické uzamykání pomocí eTags

Pokud chcete nějaký typ entity nakonfigurovat tak, aby se používalo optimistické uzamykání, volejte UseETagConcurrency. Toto volání vytvoří vlastnost _etag ve stínovém stavu a nastaví ji jako token uzamykání.

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

Pokud chcete usnadnit řešení chyb uzamykání, můžete pomocí IsETagConcurrency namapovat eTag na některou vlastnost CLR.

modelBuilder.Entity<Distributor>()
    .Property(d => d.ETag)
    .IsETagConcurrency();