Bagikan melalui


Mengonfigurasi model dengan Penyedia EF Core Azure Cosmos DB

Jenis kontainer dan entitas

Di Azure Cosmos DB, dokumen JSON disimpan dalam kontainer. Tidak seperti tabel dalam database relasional, kontainer Azure Cosmos DB dapat berisi dokumen dengan bentuk yang berbeda - kontainer tidak memberlakukan skema seragam pada dokumennya. Namun, berbagai opsi konfigurasi didefinisikan pada tingkat kontainer, dan oleh karena itu mempengaruhi semua dokumen yang terkandung di dalamnya. Lihat dokumentasi Azure Cosmos DB tentang kontainer untuk informasi selengkapnya.

Secara default, EF memetakan semua jenis entitas ke kontainer yang sama; ini biasanya merupakan default yang baik dalam hal performa dan harga. Kontainer default dinamai sesuai dengan jenis konteks .NET (OrderContext dalam hal ini). Untuk mengubah nama kontainer default, gunakan HasDefaultContainer:

modelBuilder.HasDefaultContainer("Store");

Untuk memetakan jenis entitas ke penggunaan ToContainerkontainer yang berbeda :

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

Sebelum memetakan jenis entitas ke kontainer yang berbeda, pastikan Anda memahami potensi implikasi performa dan harga (misalnya sehubungan dengan throughput khusus dan bersama); lihat dokumentasi Azure Cosmos DB untuk mempelajari selengkapnya.

ID dan kunci

Azure Cosmos DB mengharuskan semua dokumen memiliki id properti JSON yang mengidentifikasinya secara unik. Seperti penyedia EF lainnya, penyedia EF Azure Cosmos DB akan mencoba menemukan properti bernama Id atau <type name>Id, dan mengonfigurasi properti tersebut sebagai kunci jenis entitas Anda, memetakannya ke id properti JSON. Anda dapat mengonfigurasi properti apa pun untuk menjadi properti kunci dengan menggunakan HasKey; lihat dokumentasi EF umum tentang kunci untuk informasi selengkapnya.

Pengembang yang datang ke Azure Cosmos DB dari database lain terkadang mengharapkan properti kunci (Id) dibuat secara otomatis. Misalnya, pada SQL Server, EF mengonfigurasi properti kunci numerik menjadi kolom IDENTITY, di mana nilai peningkatan otomatis dihasilkan dalam database. Sebaliknya, Azure Cosmos DB tidak mendukung pembuatan properti otomatis, sehingga properti kunci harus diatur secara eksplisit. Memasukkan jenis entitas dengan properti kunci yang tidak diatur hanya akan memasukkan nilai default CLR untuk properti tersebut (misalnya 0 untuk int), dan sisipan kedua akan gagal; EF mengeluarkan peringatan jika Anda mencoba melakukan ini.

Jika Anda ingin memiliki GUID sebagai properti kunci, Anda dapat mengonfigurasi EF untuk menghasilkan nilai acak yang unik di klien:

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

Kunci partisi

Azure Cosmos DB menggunakan partisi untuk mencapai penskalaan horizontal; pemodelan yang tepat dan pemilihan kunci partisi yang cermat sangat penting untuk mencapai performa yang baik dan menjaga biaya tetap rendah. Sangat disarankan untuk membaca dokumentasi Azure Cosmos DB tentang partisi dan merencanakan strategi partisi Anda terlebih dahulu.

Untuk mengonfigurasi kunci partisi dengan EF, panggil HasPartitionKey, berikan properti reguler pada jenis entitas Anda:

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

Properti apa pun dapat dibuat menjadi kunci partisi selama dikonversi menjadi string. Setelah dikonfigurasi, properti kunci partisi harus selalu memiliki nilai non-null; mencoba menyisipkan jenis entitas baru dengan properti kunci partisi yang tidak diatur akan mengakibatkan kesalahan.

Perhatikan bahwa Azure Cosmos DB memungkinkan dua dokumen dengan properti yang sama id ada dalam kontainer, selama berada di partisi yang berbeda; ini berarti bahwa untuk mengidentifikasi dokumen secara unik dalam kontainer, properti id kunci partisi dan semuanya harus disediakan. Karena itu, gagasan internal EF tentang kunci primer entitas berisi kedua elemen ini berdasarkan konvensi, tidak seperti database relasional di mana tidak ada konsep kunci partisi. Ini berarti misalnya yang FindAsync memerlukan properti kunci dan kunci partisi (lihat dokumen lebih lanjut), dan kueri harus menentukan ini dalam klausulnyaWhere untuk mendapatkan manfaat dari efisien dan hemat point readsbiaya .

Perhatikan bahwa kunci partisi ditentukan pada tingkat kontainer. Ini terutama berarti bahwa tidak dimungkinkan bagi beberapa jenis entitas dalam kontainer yang sama untuk memiliki properti kunci partisi yang berbeda. Jika Anda perlu menentukan kunci partisi yang berbeda, petakan jenis entitas yang relevan ke kontainer yang berbeda.

Kunci partisi hierarkis

Azure Cosmos DB juga mendukung kunci partisi hierarkis untuk mengoptimalkan distribusi data lebih jauh; lihat dokumentasi untuk detail selengkapnya. EF 9.0 menambahkan dukungan untuk kunci partisi hierarkis; untuk mengonfigurasi ini, cukup teruskan hingga 3 properti ke HasPartitionKey:

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

Dengan kunci partisi hierarkis seperti itu, kueri dapat dengan mudah dikirim hanya ke subset sub-partisi yang relevan. Misalnya, jika Anda mengkueri Pesanan penyewa tertentu, kueri tersebut hanya akan dijalankan terhadap sub-partisi untuk penyewa tersebut.

Jika Anda tidak mengonfigurasi kunci partisi dengan EF, peringatan akan dicatat saat startup; EF Core akan membuat kontainer dengan kunci partisi yang diatur ke __partitionKey, dan tidak akan memberikan nilai apa pun untuknya saat menyisipkan item. Ketika tidak ada kunci partisi yang diatur, kontainer Anda akan dibatasi hingga 20 GB data, yang merupakan penyimpanan maksimum untuk satu partisi logis. Meskipun ini dapat berfungsi untuk aplikasi pengembangan/ pengujian kecil, sangat tidak dianjurkan untuk menyebarkan aplikasi produksi tanpa strategi kunci partisi yang dikonfigurasi dengan baik.

Setelah properti kunci partisi Anda dikonfigurasi dengan benar, Anda dapat memberikan nilai untuk properti tersebut dalam kueri; lihat Mengkueri dengan kunci partisi untuk informasi selengkapnya.

Diskriminator

Karena beberapa jenis entitas dapat dipetakan ke kontainer yang sama, EF Core selalu menambahkan $type properti diskriminator ke semua dokumen JSON yang Anda simpan (properti ini dipanggil Discriminator sebelum EF 9.0); ini memungkinkan EF mengenali dokumen yang dimuat dari database, dan mewujudkan jenis .NET yang tepat. Pengembang yang berasal dari database relasional mungkin terbiasa dengan diskriminator dalam konteks warisan tabel per hierarki (TPH); di Azure Cosmos DB, diskriminator digunakan tidak hanya dalam skenario pemetaan warisan, tetapi juga karena kontainer yang sama dapat berisi jenis dokumen yang sama sekali berbeda.

Nama dan nilai properti diskriminator dapat dikonfigurasi dengan API EF standar, lihat dokumen ini untuk informasi selengkapnya. Jika Anda memetakan satu jenis entitas ke kontainer, yakin bahwa Anda tidak akan pernah memetakan jenis entitas lain, dan ingin menyingkirkan properti diskriminator, hubungi HasNoDiscriminator:

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

Karena kontainer yang sama dapat berisi jenis entitas yang berbeda, dan properti JSON id harus unik dalam partisi kontainer, Anda tidak dapat memiliki nilai yang sama id untuk entitas dari berbagai jenis dalam partisi kontainer yang sama. Bandingkan ini dengan database relasional, di mana setiap jenis entitas dipetakan ke tabel yang berbeda, dan karenanya memiliki ruang kunci terpisah sendiri. Oleh karena itu, Anda bertanggung jawab untuk memastikan keunikan id dokumen yang Anda masukkan ke dalam kontainer. Jika Anda perlu memiliki jenis entitas yang berbeda dengan nilai kunci utama yang sama, Anda dapat menginstruksikan EF untuk secara otomatis memasukkan diskriminator ke id dalam properti sebagai berikut:

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

Meskipun ini mungkin membuatnya lebih mudah untuk bekerja dengan id nilai, mungkin membuatnya lebih sulit untuk beroperasi dengan aplikasi eksternal yang bekerja dengan dokumen Anda, karena sekarang mereka harus menyadari format gabungan EF id , serta nilai diskriminator, yang secara default berasal dari jenis .NET Anda. Perhatikan bahwa ini adalah perilaku default sebelum EF 9.0.

Opsi tambahan adalah menginstruksikan EF untuk menyisipkan hanya diskriminator akar, yang merupakan diskriminator dari jenis entitas akar hierarki, ke id dalam properti:

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

Ini serupa, tetapi memungkinkan EF untuk menggunakan pembacaan titik yang efisien dalam lebih banyak skenario. Jika Anda perlu memasukkan diskriminator ke id dalam properti, pertimbangkan untuk memasukkan diskriminator akar untuk performa yang lebih baik.

Throughput yang disediakan

Jika Anda menggunakan EF Core untuk membuat database atau kontainer Azure Cosmos DB, Anda dapat mengonfigurasi throughput yang disediakan untuk database dengan memanggil CosmosModelBuilderExtensions.HasAutoscaleThroughput atau CosmosModelBuilderExtensions.HasManualThroughput. Contohnya:

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

Untuk mengonfigurasi throughput yang ditentukan untuk panggilan kontainer CosmosEntityTypeBuilderExtensions.HasAutoscaleThroughput atau CosmosEntityTypeBuilderExtensions.HasManualThroughput. Contohnya:

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

Waktu hidup

Jenis entitas dalam model Azure Cosmos DB dapat dikonfigurasi dengan time-to-live default. Contohnya:

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

Atau, untuk penyimpanan analitik:

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

Time-to-live untuk entitas individual dapat diatur menggunakan properti yang dipetakan ke "ttl" dalam dokumen JSON. Contoh:

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

Catatan

Time-to-live default harus dikonfigurasi pada jenis entitas agar "ttl" memiliki efek apa pun. Lihat Time to Live (TTL) di Azure Cosmos DB untuk informasi selengkapnya.

Properti time-to-live kemudian diatur sebelum entitas disimpan. Contohnya:

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

Properti time-to-live dapat menjadi properti bayangan untuk menghindari pencemaran entitas domain dengan masalah database. Contohnya:

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

Properti bayangan time-to-live kemudian diatur dengan mengakses entitas yang dilacak. Contohnya:

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

Entitas yang disematkan

Catatan

Jenis entitas terkait dikonfigurasi sebagai milik secara default. Untuk mencegah hal ini terhadap jenis entitas tertentu, panggil ModelBuilder.Entity.

Untuk Azure Cosmos DB, entitas yang dimiliki disematkan dalam item yang sama dengan pemilik. Untuk mengubah nama properti, gunakan 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");
    });

Dengan konfigurasi ini, urutan dari contoh di atas disimpan seperti ini:

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

Koleksi entitas yang dimiliki juga disematkan. Untuk contoh berikutnya, kita akan menggunakan kelas Distributor dengan koleksi StreetAddress:

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

Entitas yang dimiliki tidak perlu memberikan nilai kunci eksplisit yang akan disimpan:

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

Nilail tersebut akan disimpan dengan cara ini:

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

Secara internal, EF Core selalu perlu memiliki nilai kunci unik untuk semua entitas yang dilacak. Kunci primer yang dibuat secara default untuk koleksi jenis yang dimiliki terdiri dari properti kunci asing yang menunjuk ke pemilik dan properti int yang sesuai dengan indeks dalam array JSON. Untuk mengambil nilai ini, entri API dapat digunakan:

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

Jika perlu, kunci primer default untuk jenis entitas yang dimiliki dapat diubah, tetapi nilai kunci harus diberikan secara eksplisit.

Koleksi jenis primitif

Kumpulan jenis primitif yang didukung, seperti string dan int, ditemukan dan dipetakan secara otomatis. Koleksi yang didukung adalah semua jenis yang mengimplementasikan IReadOnlyList<T> atau IReadOnlyDictionary<TKey,TValue>. Misalnya, pertimbangkan jenis entitas ini:

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

IList dan IDictionary dapat diisi dan dipertahankan ke database:

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

Ini menghasilkan dokumen JSON berikut:

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

Koleksi ini kemudian dapat diperbarui, sekali lagi dengan cara konvensional:

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

await context.SaveChangesAsync();

Batasan:

  • Hanya kamus dengan kunci string yang didukung.
  • Dukungan untuk kueri ke dalam koleksi primitif ditambahkan di EF Core 9.0.

Konkurensi optimis dengan eTags

Untuk mengonfigurasi jenis entitas agar menggunakan konkurensi optimis, panggil UseETagConcurrency. Panggilan ini akan membuat properti _etag dalam status bayangan dan menetapkannya sebagai token konkurensi.

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

Untuk mempermudah mengatasi kesalahan konkurensi, Anda dapat memetakan eTag ke properti CLR menggunakan IsETagConcurrency.

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