Penyedia EF Core Azure Cosmos DB

Penyedia database ini memungkinkan Entity Framework Core digunakan dengan Azure Cosmos DB. Penyedia dipertahankan sebagai bagian dari Project Core Kerangka Kerja Entitas.

Sangat disarankan untuk membiasakan diri dengan dokumentasi Azure Cosmos DB sebelum membaca bagian ini.

Catatan

Penyedia ini hanya berfungsi dengan API SQL Azure Cosmos DB.

Instal

Instal paket NuGet Microsoft.EntityFrameworkCore.Cosmos.

dotnet add package Microsoft.EntityFrameworkCore.Cosmos

Mulai

Tip

Anda dapat melihat sampel artikel ini di GitHub.

Adapun penyedia lain langkah pertama adalah memanggil UseCosmos:

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

Peringatan

Titik akhir dan kunci dikodekan secara permanen di sini untuk kesederhanaan, tetapi dalam aplikasi produksi, ini harus disimpan dengan aman.

Dalam contoh Order ini adalah entitas sederhana dengan referensi ke jenis StreetAddressyang dimiliki.

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

Menyimpan dan mengkueri data mengikuti pola EF normal:

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

Penting

Memanggil EnsureCreatedAsync diperlukan untuk membuat kontainer yang diperlukan dan menyisipkan data benih jika ada dalam model. Namun EnsureCreatedAsync hanya boleh dipanggil selama penyebaran, bukan operasi normal, karena dapat menyebabkan masalah performa.

Opsi Cosmos

Dimungkinkan juga untuk mengonfigurasi penyedia Cosmos DB dengan satu string koneksi dan menentukan opsi lain untuk menyesuaikan koneksi:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.UseCosmos(
        "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
        databaseName: "OptionsDB",
        options =>
        {
            options.ConnectionMode(ConnectionMode.Gateway);
            options.WebProxy(new WebProxy());
            options.LimitToEndpoint();
            options.Region(Regions.AustraliaCentral);
            options.GatewayModeMaxConnectionLimit(32);
            options.MaxRequestsPerTcpConnection(8);
            options.MaxTcpConnectionsPerEndpoint(16);
            options.IdleTcpConnectionTimeout(TimeSpan.FromMinutes(1));
            options.OpenTcpConnectionTimeout(TimeSpan.FromMinutes(1));
            options.RequestTimeout(TimeSpan.FromMinutes(1));
        });

Catatan

Sebagian besar opsi ini diperkenalkan di EF Core 5.0.

Tip

Lihat dokumentasi Opsi Azure Cosmos DB untuk deskripsi terperinci tentang efek dari setiap opsi yang disebutkan di atas.

Kustomisasi model khusus Kosmos

Secara default semua jenis entitas dipetakan ke kontainer yang sama, dinamai sesuai dengan konteks turunan ("OrderContext" dalam hal ini). Untuk mengubah nama kontainer default, gunakan HasDefaultContainer:

modelBuilder.HasDefaultContainer("Store");

Untuk memetakan jenis entitas ke kontainer lain, gunakan ToContainer:

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

Untuk mengidentifikasi jenis entitas yang diwakili item tertentu, EF Core menambahkan nilai diskriminator meskipun tidak ada jenis entitas turunan. Nama dan nilai diskriminator dapat diubah.

Jika tidak ada jenis entitas lain yang akan disimpan dalam kontainer yang sama, diskriminator dapat dihapus dengan memanggil HasNoDiscriminator:

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

Kunci partisi

Secara default EF Core akan membuat kontainer dengan kunci partisi diatur ke "__partitionKey" tanpa menyediakan nilai apa pun untuknya saat menyisipkan item. Tetapi untuk sepenuhnya memanfaatkan kemampuan performa Azure Cosmos , kunci partisi yang dipilih dengan hati-hati harus digunakan. Ini dapat dikonfigurasi dengan memanggil HasPartitionKey:

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

Catatan

Properti kunci partisi dapat memiliki jenis apa pun selama dikonversi menjadi string.

Setelah dikonfigurasi, properti kunci partisi harus selalu memiliki nilai non-null. Kueri dapat dibuat partisi tunggal dengan menambahkan WithPartitionKey panggilan.

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.WithPartitionKey("2").LastAsync();
    Console.WriteLine($"Last order will ship to: {order.ShippingAddress.Street}, {order.ShippingAddress.City}");
    Console.WriteLine();
}

Catatan

WithPartitionKey diperkenalkan di EF Core 5.0.

Umumnya disarankan untuk menambahkan kunci partisi ke kunci primer karena yang paling mencerminkan semantik server dan memungkinkan beberapa pengoptimalan, misalnya di FindAsync.

Throughput yang disediakan

Jika Anda menggunakan EF Core untuk membuat database atau kontainer Azure Cosmos, 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 disediakan untuk panggilan CosmosEntityTypeBuilderExtensions.HasAutoscaleThroughput kontainer atau CosmosEntityTypeBuilderExtensions.HasManualThroughput. Contohnya:

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

Entitas yang disematkan

Catatan

Dimulai dengan jenis entitas terkait EF Core 6.0 dikonfigurasi sebagai milik secara default. Untuk mencegah ini untuk panggilan ModelBuilder.Entityjenis entitas tertentu .

Untuk Cosmos, entitas yang dimiliki disematkan dalam item yang sama dengan pemiliknya. 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 Distributor kelas 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 untuk 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();
}

Mereka akan bertahan 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 yang unik untuk semua entitas yang dilacak. Kunci primer yang dibuat secara default untuk kumpulan jenis yang dimiliki terdiri dari properti kunci asing yang menunjuk ke pemilik dan properti yang int sesuai dengan indeks dalam array JSON. Untuk mengambil API entri nilai ini 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 kemudian nilai kunci harus disediakan 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; }
}

Daftar dan kamus dapat diisi dan disisipkan ke dalam database dengan cara normal:

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);
context.SaveChanges();

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 normal:

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

context.SaveChanges();

Keterbatasan:

  • Hanya kamus dengan kunci string yang didukung
  • Kueri ke dalam konten koleksi primitif saat ini tidak didukung. Pilih #16926, #25700, dan #25701 jika fitur ini penting bagi Anda.

Bekerja dengan entitas yang terputus

Setiap item harus memiliki id nilai yang unik untuk kunci partisi yang diberikan. Secara default EF Core menghasilkan nilai dengan menggabungkan diskriminator dan nilai kunci primer, menggunakan '|' sebagai pemisah. Nilai kunci hanya dihasilkan saat entitas memasuki status Added . Ini mungkin menimbulkan masalah saat melampirkan entitas jika mereka tidak memiliki id properti pada jenis .NET untuk menyimpan nilai.

Untuk mengatasi batasan ini, seseorang dapat membuat dan mengatur id nilai secara manual atau menandai entitas sebagai ditambahkan terlebih dahulu, lalu mengubahnya ke status yang diinginkan:

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

Ini adalah JSON yang dihasilkan:

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

Konkurensi optimis dengan eTags

Catatan

Dukungan untuk konkurensi eTag diperkenalkan di EF Core 5.0.

Untuk mengonfigurasi jenis entitas untuk menggunakan panggilan UseETagConcurrencykonkurensi optimis . Panggilan ini akan membuat _etag properti dalam status bayangan dan mengaturnya 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();