Udostępnij za pośrednictwem


Konfigurowanie modelu przy użyciu dostawcy usługi Azure Cosmos DB platformy EF Core

Kontenery i typy jednostek

W usłudze Azure Cosmos DB dokumenty JSON są przechowywane w kontenerach. W przeciwieństwie do tabel w relacyjnych bazach danych kontenery usługi Azure Cosmos DB mogą zawierać dokumenty o różnych kształtach — kontener nie nakłada jednolitego schematu na jego dokumenty. Jednak różne opcje konfiguracji są definiowane na poziomie kontenera i w związku z tym mają wpływ na wszystkie zawarte w nim dokumenty. Aby uzyskać więcej informacji, zobacz dokumentację usługi Azure Cosmos DB dotyczącą kontenerów .

Domyślnie program EF mapuje wszystkie typy jednostek na ten sam kontener; jest to zwykle dobra wartość domyślna pod względem wydajności i cen. Domyślny kontener ma nazwę po typie kontekstu platformy .NET (OrderContext w tym przypadku). Aby zmienić domyślną nazwę kontenera, użyj polecenia HasDefaultContainer:

modelBuilder.HasDefaultContainer("Store");

Aby zamapować typ jednostki na inny kontener, użyj polecenia ToContainer:

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

Przed mapowaniem typów jednostek na różne kontenery upewnij się, że rozumiesz potencjalne konsekwencje dotyczące wydajności i cen (np. w odniesieniu do dedykowanej i udostępnionej przepływności); Aby dowiedzieć się więcej, zobacz dokumentację usługi Azure Cosmos DB.

Identyfikatory i klucze

Usługa Azure Cosmos DB wymaga, aby wszystkie dokumenty miały id właściwość JSON, która jednoznacznie je identyfikuje. Podobnie jak inni dostawcy platformy EF, dostawca usługi EF Azure Cosmos DB podejmie próbę znalezienia właściwości o nazwie Id lub <type name>Idi skonfiguruje ją jako klucz typu jednostki, mapuje ją na id właściwość JSON. Aby uzyskać więcej informacji, możesz skonfigurować dowolną właściwość jako właściwość klucza przy użyciu polecenia HasKey. Aby uzyskać więcej informacji, zobacz ogólną dokumentację platformy EF dotyczącą kluczy .

Deweloperzy przechodzący do usługi Azure Cosmos DB z innych baz danych czasami oczekują, że właściwość klucza (Id) zostanie wygenerowana automatycznie. Na przykład w programie SQL Server program EF konfiguruje właściwości klucza liczbowego jako kolumny IDENTITY, w których wartości automatycznego zwiększania są generowane w bazie danych. Z kolei usługa Azure Cosmos DB nie obsługuje automatycznego generowania właściwości, dlatego kluczowe właściwości muszą być jawnie ustawione. Wstawianie typu jednostki z niezastawioną właściwością klucza spowoduje po prostu wstawienie wartości domyślnej CLR dla tej właściwości (np. 0 dla int), a drugie wstawienie zakończy się niepowodzeniem; Program EF wyświetla ostrzeżenie, jeśli spróbujesz to zrobić.

Jeśli chcesz mieć identyfikator GUID jako właściwość klucza, możesz skonfigurować program EF do generowania unikatowych, losowych wartości na kliencie:

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

Klucze partycji

Usługa Azure Cosmos DB używa partycjonowania do osiągnięcia skalowania w poziomie; odpowiednie modelowanie i staranne wybieranie klucza partycji jest niezbędne do osiągnięcia dobrej wydajności i utrzymania kosztów w dół. Zdecydowanie zaleca się przeczytanie dokumentacji usługi Azure Cosmos DB dotyczącej partycjonowania i zaplanowanie strategii partycjonowania z wyprzedzeniem.

Aby skonfigurować klucz partycji za pomocą platformy EF, wywołaj metodę HasPartitionKey, przekazując jej zwykłą właściwość dla typu jednostki:

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

Dowolną właściwość można utworzyć w kluczu partycji, o ile jest konwertowana na ciąg. Po skonfigurowaniu właściwość klucza partycji powinna zawsze mieć wartość inną niż null; Próba wstawienia nowego typu jednostki z niezastawioną właściwością klucza partycji spowoduje wystąpienie błędu.

Należy pamiętać, że usługa Azure Cosmos DB umożliwia istnienie dwóch dokumentów z tą samą id właściwością w kontenerze, o ile znajdują się one w różnych partycjach. Oznacza to, że w celu unikatowego zidentyfikowania dokumentu w kontenerze należy podać zarówno id właściwości klucza partycji, jak i klucza partycji. W związku z tym wewnętrzne pojęcie klucza podstawowego jednostki ef zawiera oba te elementy zgodnie z konwencją, w przeciwieństwie do relacyjnych baz danych, w których nie ma pojęcia klucza partycji. Oznacza to na przykład, że FindAsync wymaga zarówno właściwości klucza, jak i klucza partycji (zobacz dalsze dokumenty), a zapytanie musi określić je w klauzuli Where , aby korzystać z wydajnego i ekonomicznego point reads.

Należy pamiętać, że klucz partycji jest zdefiniowany na poziomie kontenera. Oznacza to w szczególności, że nie jest możliwe, aby wiele typów jednostek w tym samym kontenerze miało różne właściwości klucza partycji. Jeśli musisz zdefiniować różne klucze partycji, zamapuj odpowiednie typy jednostek na różne kontenery.

Hierarchiczne klucze partycji

Usługa Azure Cosmos DB obsługuje również hierarchiczne klucze partycji, aby jeszcze bardziej zoptymalizować dystrybucję danych; Aby uzyskać więcej informacji, zobacz dokumentację. Program EF 9.0 dodał obsługę kluczy partycji hierarchicznych; aby je skonfigurować, wystarczy przekazać do 3 właściwości hasPartitionKey:

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

W przypadku takiego hierarchicznego klucza partycji zapytania można łatwo wysyłać tylko do odpowiedniego podzestawu partycji podrzędnych. Jeśli na przykład wykonasz zapytanie dotyczące zamówień określonej dzierżawy, te zapytania będą wykonywane tylko względem pod partycji dla tej dzierżawy.

Jeśli nie skonfigurujesz klucza partycji za pomocą programu EF, podczas uruchamiania zostanie zarejestrowane ostrzeżenie; Program EF Core utworzy kontenery z kluczem partycji ustawionym na __partitionKeywartość i nie podasz żadnej wartości podczas wstawiania elementów. Jeśli żaden klucz partycji nie zostanie ustawiony, kontener będzie ograniczony do 20 GB danych, co jest maksymalnym magazynem dla pojedynczej partycji logicznej. Chociaż może to działać w przypadku małych aplikacji deweloperskich/testowych, zdecydowanie nie zaleca się wdrażania aplikacji produkcyjnej bez dobrze skonfigurowanej strategii klucza partycji.

Po poprawnym skonfigurowaniu właściwości klucza partycji można podać wartości dla nich w zapytaniach; Aby uzyskać więcej informacji, zobacz Wykonywanie zapytań za pomocą kluczy partycji.

Dyskryminujące

Ponieważ wiele typów jednostek może być mapowanych na ten sam kontener, program EF Core zawsze dodaje $type właściwość dyskryminującą do wszystkich zapisanych dokumentów JSON (ta właściwość została wywołana Discriminator przed ef 9.0); umożliwia to programowi EF rozpoznawanie dokumentów ładowanych z bazy danych i materializowanie odpowiedniego typu platformy .NET. Deweloperzy pochodzący z relacyjnych baz danych mogą zapoznać się z dyskryminatorami w kontekście dziedziczenia tabeli na hierarchię (TPH), a w usłudze Azure Cosmos DB dyskryminujące są używane nie tylko w scenariuszach mapowania dziedziczenia, ale także dlatego, że ten sam kontener może zawierać zupełnie różne typy dokumentów.

Aby uzyskać więcej informacji, zobacz te dokumenty, aby uzyskać więcej informacji, można skonfigurować nazwy i wartości właściwości dyskryminującej przy użyciu standardowych interfejsów API ef. Jeśli mapujesz pojedynczy typ jednostki na kontener, masz pewność, że nigdy nie będziesz mapować innego typu i chcesz pozbyć się właściwości dyskryminującej, wywołaj metodę HasNoDiscriminator:

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

Ponieważ ten sam kontener może zawierać różne typy jednostek, a właściwość JSON id musi być unikatowa w ramach partycji kontenera, nie można mieć tej samej id wartości dla jednostek różnych typów w tej samej partycji kontenera. Porównaj to z relacyjnymi bazami danych, w których każdy typ jednostki jest mapowany na inną tabelę i dlatego ma własną, oddzielną przestrzeń klucza. W związku z tym twoim obowiązkiem jest zapewnienie id unikatowości dokumentów wstawianych do kontenera. Jeśli musisz mieć różne typy jednostek z tymi samymi wartościami klucza podstawowego, możesz poinstruować program EF, aby automatycznie wstawić dyskryminujący do id właściwości w następujący sposób:

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

Chociaż może to ułatwić pracę z wartościami, może utrudnić współdziałanie z aplikacjami zewnętrznymi pracującymi z id dokumentami, ponieważ teraz muszą być świadomi formatu połączonego id programu EF, a także wartości dyskryminujących, które są domyślnie pochodzące z typów platformy .NET. Należy pamiętać, że było to zachowanie domyślne przed programem EF 9.0.

Dodatkową opcją jest poinstruowanie programu EF o wstawieniu tylko dyskryminujących głównych dyskryminujących typu jednostki głównej hierarchii do id właściwości :

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

Jest to podobne, ale umożliwia ef korzystanie z wydajnych odczytów punktów w kolejnych scenariuszach. Jeśli musisz wstawić dyskryminator do id właściwości, rozważ wstawienie głównego dyskryminatora w celu uzyskania lepszej wydajności.

Aprowizowana przepływność

Jeśli używasz programu EF Core do tworzenia bazy danych lub kontenerów usługi Azure Cosmos DB, możesz skonfigurować aprowizowaną przepływność dla bazy danych przez wywołanie metody CosmosModelBuilderExtensions.HasAutoscaleThroughput lub CosmosModelBuilderExtensions.HasManualThroughput. Na przykład:

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

Aby skonfigurować aprowizowaną przepływność dla kontenera, wywołaj metodę CosmosEntityTypeBuilderExtensions.HasAutoscaleThroughput lub CosmosEntityTypeBuilderExtensions.HasManualThroughput. Na przykład:

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

Czas wygaśnięcia

Typy jednostek w modelu usługi Azure Cosmos DB można skonfigurować przy użyciu domyślnego czasu wygaśnięcia. Na przykład:

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

Lub dla magazynu analitycznego:

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

Czas wygaśnięcia dla poszczególnych jednostek można ustawić przy użyciu właściwości zamapowanej na wartość "ttl" w dokumencie JSON. Na przykład:

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

Uwaga

Domyślny czas wygaśnięcia musi być skonfigurowany dla typu jednostki "czas wygaśnięcia", aby mieć jakikolwiek wpływ. Aby uzyskać więcej informacji, zobacz Czas wygaśnięcia (TTL) w usłudze Azure Cosmos DB .

Właściwość time-to-live jest następnie ustawiana przed zapisaną jednostką. Na przykład:

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

Właściwość time-to-live może być właściwością w tle, aby uniknąć zanieczyszczania jednostki domeny z obawami dotyczącymi bazy danych. Na przykład:

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

Właściwość czas wygaśnięcia w tle jest następnie ustawiana przez uzyskanie dostępu do śledzonej jednostki. Na przykład:

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

Jednostki osadzone

Uwaga

Powiązane typy jednostek są domyślnie konfigurowane jako należące do użytkownika. Aby temu zapobiec dla określonego typu jednostki, wywołaj metodę ModelBuilder.Entity.

W przypadku usługi Azure Cosmos DB jednostki należące do firmy są osadzone w tym samym elemencie co właściciel. Aby zmienić nazwę właściwości, użyj metody 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");
    });

W przypadku tej konfiguracji zamówienie z powyższego przykładu jest zapisywane w następujący sposób:

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

Kolekcje jednostek własnościowych również są osadzone. W następnym przykładzie użyjemy klasy Distributor z kolekcją obiektów StreetAddress:

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

Jednostki własnościowe nie muszą udostępniać jawnych wartości klucza na potrzeby przechowywania:

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

Będą one utrwalane w ten sposób:

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

Wewnętrznie rozwiązanie EF Core zawsze musi mieć unikatowe wartości klucza dla wszystkich śledzonych jednostek. Klucz podstawowy tworzony domyślnie dla kolekcji typów własnościowych zawiera właściwość klucza obcego wskazującą właściciela oraz właściwość int odpowiadającą indeksowi w tablicy JSON. Aby pobrać te wartości, można użyć interfejsu API wpisów:

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

Napiwek

W razie potrzeby można zmienić domyślny klucz podstawowy dla własnościowych typów jednostek, ale wówczas konieczne jest jawne podawanie wartości kluczy.

Kolekcje typów pierwotnych

Kolekcje obsługiwanych typów pierwotnych, takich jak string i int, są odnajdywane i mapowane automatycznie. Obsługa kolekcji obejmuje wszystkie typy implementujące interfejs IReadOnlyList<T> lub IReadOnlyDictionary<TKey,TValue>. Rozważmy na przykład ten typ jednostki:

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

Element IList i IDictionary można wypełnić i utrwały w bazie danych:

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

Spowoduje to utworzenie następującego dokumentu 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
}

Te kolekcje można następnie aktualizować, również w normalny sposób:

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

await context.SaveChangesAsync();

Ograniczenia:

  • Obsługiwane są tylko słowniki z kluczami ciągów.
  • Dodano obsługę wykonywania zapytań dotyczących kolekcji pierwotnych w programie EF Core 9.0.

Optymistyczna współbieżność przy użyciu elementów eTag

Aby skonfigurować typ jednostki w celu używania optymistycznej współbieżności, wywołaj metodę UseETagConcurrency. To wywołanie spowoduje utworzenie właściwości _etag w stanie towarzyszącym i ustawienie jej jako tokenu współbieżności.

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

Aby ułatwić usuwanie błędów współbieżności, możesz zamapować element eTag na właściwość CLR przy użyciu metody IsETagConcurrency.

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