Bagikan melalui


Mengkueri dengan Penyedia EF Core Azure Cosmos DB

Dasar-dasar kueri

Kueri EF Core LINQ dapat dijalankan terhadap Azure Cosmos DB dengan cara yang sama seperti untuk penyedia database lainnya. Contoh:

public class Session
{
    public Guid Id { get; set; }
    public string Category { get; set; }

    public string TenantId { get; set; } = null!;
    public Guid UserId { get; set; }
    public int SessionId { get; set; }
}

var stringResults = await context.Sessions
    .Where(
        e => e.Category.Length > 4
            && e.Category.Trim().ToLower() != "disabled"
            && e.Category.TrimStart().Substring(2, 2).Equals("xy", StringComparison.OrdinalIgnoreCase))
    .ToListAsync();

Catatan

Penyedia Azure Cosmos DB tidak menerjemahkan kumpulan kueri LINQ yang sama dengan penyedia lain. Misalnya, operator EF Include() tidak didukung di Azure Cosmos DB, karena kueri lintas dokumen tidak didukung dalam database.

Kunci partisi

Keuntungan dari pemartisian adalah agar kueri Anda dijalankan hanya terhadap partisi tempat data yang relevan ditemukan, menghemat biaya, dan memastikan kecepatan hasil yang lebih cepat. Kueri yang tidak menentukan kunci partisi dijalankan pada semua partisi, yang bisa sangat mahal.

Dimulai dengan EF 9.0, EF secara otomatis mendeteksi dan mengekstrak perbandingan kunci partisi di operator kueri Where LINQ Anda. Mari kita asumsikan kita menjalankan kueri berikut terhadap jenis entitas kita Session , yang dikonfigurasi dengan kunci partisi hierarkis:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Session>()
        .HasPartitionKey(b => new { b.TenantId, b.UserId, b.SessionId })
}

var tenantId = "Microsoft";
var userId = new Guid("99A410D7-E467-4CC5-92DE-148F3FC53F4C");
var username = "scott";

var sessions = await context.Sessions
    .Where(
        e => e.TenantId == tenantId
             && e.UserId == userId
             && e.SessionId > 0
             && e.Username == username)
    .ToListAsync();

Memeriksa log yang dihasilkan oleh EF, kita melihat kueri ini dijalankan sebagai berikut:

Executed ReadNext (166.6985 ms, 2.8 RU) ActivityId='312da0d2-095c-4e73-afab-27072b5ad33c', Container='test', Partition='["Microsoft","99a410d7-e467-4cc5-92de-148f3fc53f4c"]', Parameters=[]
SELECT VALUE c
FROM root c
WHERE ((c["SessionId"] > 0) AND CONTAINS(c["Username"], "a"))

Dalam log ini, kami melihat hal berikut:

  • Dua perbandingan pertama - pada TenantId dan UserId - telah dicabut, dan muncul di ReadNext "Partisi" daripada dalam WHERE klausul; ini berarti bahwa kueri hanya akan dijalankan pada subpartisi untuk nilai-nilai tersebut.
  • SessionId juga merupakan bagian dari kunci partisi hierarkis, tetapi alih-alih perbandingan kesetaraan, ia menggunakan operator yang lebih besar dari (>), dan oleh karena itu tidak dapat diangkat. Ini adalah bagian WHERE dari klausul seperti properti biasa.
  • Username adalah properti reguler - bukan bagian dari kunci partisi - dan oleh karena itu tetap dalam WHERE klausul juga.

Perhatikan bahwa meskipun beberapa nilai kunci partisi tidak disediakan, kunci partisi hierarkis masih memungkinkan penargetan hanya subpartisi yang sesuai dengan dua properti pertama. Meskipun ini tidak seefisien menargetkan satu partisi (seperti yang diidentifikasi oleh ketiga properti), ini masih jauh lebih efisien daripada menargetkan semua partisi.

Daripada mereferensikan properti kunci partisi di Where operator, Anda dapat secara eksplisit menentukannya dengan menggunakan WithPartitionKey operator:

var sessions = await context.Sessions
    .WithPartitionKey(tenantId, userId)
    .Where(e => e.SessionId > 0 && e.Username.Contains("a"))
    .ToListAsync();

Ini dijalankan dengan cara yang sama seperti kueri di atas, dan dapat lebih disukai jika Anda ingin membuat kunci partisi lebih eksplisit dalam kueri Anda. Penggunaan WithPartitionKey mungkin diperlukan dalam versi EF sebelum 9.0 - perhatikan log untuk memastikan bahwa kueri menggunakan kunci partisi seperti yang diharapkan.

Baca titik

Meskipun Azure Cosmos DB memungkinkan kueri yang kuat melalui SQL, kueri tersebut bisa sangat mahal. Azure Cosmos DB juga mendukung pembacaan titik, yang harus digunakan saat mengambil satu dokumen jika id properti dan seluruh kunci partisi diketahui. Point reads secara langsung mengidentifikasi dokumen tertentu dalam partisi tertentu, dan menjalankan dengan sangat efisien dan dengan pengurangan biaya dibandingkan dengan mengambil dokumen yang sama dengan kueri. Disarankan untuk merancang sistem Anda untuk memanfaatkan pembacaan titik sesering mungkin. Untuk membaca selengkapnya, lihat dokumentasi Azure Cosmos DB.

Di bagian sebelumnya, kami melihat EF mengidentifikasi dan mengekstrak perbandingan kunci partisi dari Where klausul untuk kueri yang lebih efisien, membatasi pemrosesan hanya ke partisi yang relevan. Dimungkinkan untuk melangkah lebih jauh, dan menyediakan id properti dalam kueri juga. Mari kita periksa kueri berikut:

var session = await context.Sessions.SingleAsync(
    e => e.Id == someId
         && e.TenantId == tenantId
         && e.UserId == userId
         && e.SessionId == sessionId);

Dalam kueri ini, nilai untuk Id properti disediakan (yang dipetakan ke properti Azure Cosmos DB id ), serta nilai untuk semua properti kunci partisi. Selain itu, tidak ada komponen tambahan untuk kueri. Ketika semua kondisi ini terpenuhi, EF dapat menjalankan kueri sebagai titik baca:

Executed ReadItem (46 ms, 1 RU) ActivityId='d7391311-2266-4811-ae2d-535904c42c43', Container='test', Id='9', Partition='["Microsoft","99a410d7-e467-4cc5-92de-148f3fc53f4c",10.0]'

ReadItemPerhatikan , yang menunjukkan bahwa kueri dijalankan sebagai titik baca yang efisien - tidak ada kueri SQL yang terlibat.

Perhatikan bahwa seperti ekstraksi kunci partisi, peningkatan signifikan telah dilakukan pada mekanisme ini di EF 9.0; versi lama tidak mendeteksi dan menggunakan pembacaan titik dengan andal.

Penentuan halaman

Catatan

Fitur ini diperkenalkan di EF Core 9.0 dan masih bersifat eksperimental. Beri tahu kami cara kerjanya untuk Anda dan jika Anda memiliki umpan balik.

Penomoran halaman mengacu pada pengambilan hasil di halaman, bukan sekaligus; ini biasanya dilakukan untuk hasil besar, di mana antarmuka pengguna ditampilkan, memungkinkan pengguna untuk menavigasi melalui halaman hasil.

Cara umum untuk menerapkan penomoran halaman dengan database adalah dengan menggunakan Skip operator LINQ dan Take (OFFSET dan LIMIT di SQL). Mengingat ukuran halaman 10 hasil, halaman ketiga dapat diambil dengan EF Core sebagai berikut:

var position = 20;
var nextPage = await context.Session
    .OrderBy(s => s.Id)
    .Skip(position)
    .Take(10)
    .ToListAsync();

Sayangnya, teknik ini cukup tidak efisien dan dapat meningkatkan biaya kueri secara besar-besaran. Azure Cosmos DB menyediakan mekanisme khusus untuk paginating melalui hasil kueri, melalui penggunaan token kelanjutan:

CosmosPage firstPage = await context.Sessions
    .OrderBy(s => s.Id)
    .ToPageAsync(pageSize: 10, continuationToken: null);

string continuationToken = firstPage.ContinuationToken;
foreach (var session in firstPage.Values)
{
    // Display/send the sessions to the user
}

Daripada mengakhiri kueri LINQ dengan ToListAsync atau serupa, kami menggunakan metode , ToPageAsync menginstruksikannya untuk mendapatkan paling banyak 10 item di setiap halaman (perhatikan bahwa mungkin ada lebih sedikit item dalam database). Karena ini adalah kueri pertama kami, kami ingin mendapatkan hasil dari awal, dan meneruskan null sebagai token kelanjutan. ToPageAsyncCosmosPagemengembalikan , yang mengekspos token kelanjutan dan nilai di halaman (hingga 10 item). Program Anda biasanya akan mengirim nilai-nilai tersebut ke klien, bersama dengan token kelanjutan; ini akan memungkinkan lanjutan kueri nanti dan mengambil lebih banyak hasil.

Mari kita asumsikan pengguna sekarang mengklik tombol "Berikutnya" di UI mereka, meminta 10 item berikutnya. Anda kemudian dapat menjalankan kueri sebagai berikut:

CosmosPage nextPage = await context.Sessions.OrderBy(s => s.Id).ToPageAsync(10, continuationToken);
string continuationToken = nextPage.ContinuationToken;
foreach (var session in nextPage.Values)
{
    // Display/send the sessions to the user
}

Kami menjalankan kueri yang sama, tetapi kali ini kami meneruskan token kelanjutan yang diterima dari eksekusi pertama; ini menginstruksikan mesin kueri untuk melanjutkan kueri yang ditinggalkannya, dan mengambil 10 item berikutnya. Setelah kita mengambil halaman terakhir dan tidak ada lagi hasil, token kelanjutannya adalah null, dan tombol "Berikutnya" dapat berwarna abu-abu. Metode paginating ini sangat efisien dan hemat biaya dibandingkan dengan menggunakan Skip dan Take.

Untuk mempelajari selengkapnya tentang penomoran halaman di Azure Cosmos DB, lihat halaman ini.

Catatan

Azure Cosmos DB tidak mendukung penomoran halaman mundur, dan tidak menyediakan hitungan total halaman atau item.

ToPageAsync saat ini diannotasikan sebagai eksperimental, karena dapat diganti dengan API penomoran halaman EF yang lebih umum yang tidak spesifik untuk Azure Cosmos DB. Meskipun menggunakan API saat ini akan menghasilkan peringatan kompilasi (EF9102), melakukannya harus aman - perubahan di masa mendatang mungkin memerlukan perubahan kecil dalam bentuk API.

FindAsync

FindAsync adalah API yang berguna untuk mendapatkan entitas dengan kunci utamanya, dan menghindari roundtrip database ketika entitas telah dimuat dan dilacak oleh konteks.

Pengembang yang terbiasa dengan database relasional digunakan untuk kunci utama jenis entitas yang terdiri dari properti.Id Saat menggunakan penyedia EF Azure Cosmos DB, kunci utama berisi properti kunci partisi selain properti yang dipetakan ke properti JSON id ; ini adalah kasus karena Azure Cosmos DB memungkinkan partisi yang berbeda untuk berisi dokumen dengan properti JSON id yang sama, sehingga hanya kunci gabungan id dan partisi yang secara unik mengidentifikasi satu dokumen dalam kontainer:

public class Session
{
    public Guid Id { get; set; }
    public string PartitionKey { get; set; }
    ...
}

var mySession = await context.FindAsync(id, pkey);

Jika Anda memiliki kunci partisi hierarkis, Anda harus meneruskan semua nilai kunci partisi ke FindAsync, dalam urutan dikonfigurasi.

Catatan

Gunakan FindAsync hanya ketika entitas mungkin sudah dilacak oleh konteks Anda, dan Anda ingin menghindari perjalanan pulang-pergi database. Jika tidak, cukup gunakan SingleAsync - tidak ada perbedaan performa antara keduanya ketika entitas perlu dimuat dari database.

Kueri SQL

Kueri juga dapat ditulis langsung di SQL. Contohnya:

var rating = 3;
_ = await context.Blogs
    .FromSql($"SELECT VALUE c FROM root c WHERE c.Rating > {rating}")
    .ToListAsync();

Kueri ini menghasilkan eksekusi kueri berikut:

SELECT VALUE s
FROM (
    SELECT VALUE c FROM root c WHERE c.Angle1 <= @p0
) s

Perhatikan bahwa FromSql diperkenalkan di EF 9.0. Di versi sebelumnya, FromSqlRaw dapat digunakan sebagai gantinya, meskipun perhatikan bahwa metode tersebut rentan terhadap serangan injeksi SQL.

Untuk informasi selengkapnya tentang kueri SQL, lihat dokumentasi relasional tentang kueri SQL; sebagian besar konten tersebut juga relevan untuk penyedia Azure Cosmos DB.

Pemetaan fungsi

Bagian ini menunjukkan metode dan anggota .NET mana yang diterjemahkan ke dalam fungsi SQL mana saat mengkueri dengan penyedia Azure Cosmos DB.

Fungsi tanggal dan waktu

.NET SQL Ditambahkan dalam
DateTime.UtcNow GetCurrentDateTime()
DateTimeOffset.UtcNow GetCurrentDateTime()
dateTime.Tahun1 DateTimePart("yyyy", dateTime) EF Core 9.0
dateTimeOffset.Tahun1 DateTimePart("yyyy", dateTimeOffset) EF Core 9.0
dateTime.AddYears(years)1 DateTimeAdd("yyyy", dateTime) EF Core 9.0
dateTimeOffset.AddYears(years)1 DateTimeAdd("yyyy", dateTimeOffset) EF Core 9.0

1 Anggota komponen lainnya juga diterjemahkan (Bulan, Hari...).

Fungsi numerik

.NET SQL Ditambahkan dalam
dobel. DegreesToRadians(x) RADIANS(@x) EF Core 8.0
dobel. RadiansToDegrees(x) DEGREES(@x) EF Core 8.0
EF. Functions.Random() RAND()
Math.Abs(value) ABS(@value)
Math.Acos(d) ACOS(@d)
Math.Asin(d) ASIN(@d)
Math.Atan(d) ATAN(@d)
Math.Atan2(y, x) ATN2(@y, @x)
Math.Ceiling(d) CEILING(@d)
Math.Cos(d) COS(@d)
Math.Exp(d) EXP(@d)
Math.Floor(d) FLOOR(@d)
Math.Log(a, newBase) LOG(@a, @newBase)
Math.Log(d) LOG(@d)
Math.Log10(d) LOG10(@d)
Math.Pow(x, y) POWER(@x, @y)
Math.Round(d) ROUND(@d)
Math.Sign(value) SIGN(@value)
Math.Sin(a) SIN(@a)
Math.Sqrt(d) SQRT(@d)
Math.Tan(a) TAN(@a)
Math.Truncate(d) TRUNC(@d)

Tip

Selain metode yang tercantum di sini, implementasi matematika generik yang sesuai dan metode MathF juga diterjemahkan. Misalnya, Math.Sin, , MathF.Sindouble.Sin, dan float.Sin semua peta ke SIN fungsi di SQL.

Fungsi string

.NET SQL Ditambahkan dalam
Regex.IsMatch(input, pattern) RegexMatch(@pattern, @input) EF Core 7.0
Regex.IsMatch(input, pattern, options) RegexMatch(@input, @pattern, @options) EF Core 7.0
tali. Concat(str0, str1) @str0 + @str1
tali. Sama dengan(a, b, StringComparison.Ordinal) STRINGEQUALS(@a, @b)
tali. Sama dengan(a, b, StringComparison.OrdinalIgnoreCase) STRINGEQUALS(@a, @b, true)
stringValue.Contains(value) CONTAINS(@stringValue, @value)
stringValue.Contains(value, StringComparison.Ordinal) CONTAINS(@stringValue, @value, false) EF Core 9.0
stringValue.Contains(value, StringComparison.OrdinalIgnoreCase) CONTAINS(@stringValue, @value, true) EF Core 9.0
stringValue.EndsWith(value) ENDSWITH(@stringValue, @value)
stringValue.EndsWith(value, StringComparison.Ordinal) ENDSWITH(@stringValue, @value, false) EF Core 9.0
stringValue.EndsWith(value, StringComparison.OrdinalIgnoreCase) ENDSWITH(@stringValue, @value, true) EF Core 9.0
stringValue.Equals(value, StringComparison.Ordinal) STRINGEQUALS(@stringValue, @value)
stringValue.Equals(value, StringComparison.OrdinalIgnoreCase) STRINGEQUALS(@stringValue, @value, true)
stringValue.FirstOrDefault() LEFT(@stringValue, 1)
stringValue.IndexOf(value) INDEX_OF(@stringValue, @value)
stringValue.IndexOf(value, startIndex) INDEX_OF(@stringValue, @value, @startIndex)
stringValue.LastOrDefault() RIGHT(@stringValue, 1)
stringValue.Length LENGTH(@stringValue)
stringValue.Replace(oldValue, newValue) REPLACE(@stringValue, @oldValue, @newValue)
stringValue.StartsWith(value) STARTSWITH(@stringValue, @value)
stringValue.StartsWith(value, StringComparison.Ordinal) STARTSWITH(@stringValue, @value, false) EF Core 9.0
stringValue.StartsWith(value, StringComparison.OrdinalIgnoreCase) STARTSWITH(@stringValue, @value, true) EF Core 9.0
stringValue.Substring(startIndex) SUBSTRING(@stringValue, @startIndex, LENGTH(@stringValue))
stringValue.Substring(startIndex, length) SUBSTRING(@stringValue, @startIndex, @length)
stringValue.ToLower() LOWER(@stringValue)
stringValue.ToUpper() UPPER(@stringValue)
stringValue.Trim() TRIM(@stringValue)
stringValue.TrimEnd() RTRIM(@stringValue)
stringValue.TrimStart() LTRIM(@stringValue)

Fungsi lain

.NET SQL Catatan
koleksi. Berisi(item) @item DI @collection
EF. Functions.CoalesceUndefined(x, y)1 x ?? y Ditambahkan dalam EF Core 9.0
EF. Functions.IsDefined(x) IS_DEFINED(x) Ditambahkan dalam EF Core 9.0
EF. Functions.VectorDistance(vector1, vector2)2 VectorDistance (vector1, vector2) Ditambahkan dalam EF Core 9.0, Eksperimental
EF. Functions.VectorDistance(vector1, vector2, bruteForce)2 VectorDistance (vector1, vector2, bruteForce) Ditambahkan dalam EF Core 9.0, Eksperimental
EF. Functions.VectorDistance(vector1, vector2, bruteForce, distanceFunction)2 VectorDistance(vector1, vector2, bruteForce, distanceFunction) Ditambahkan dalam EF Core 9.0, Eksperimental

1 Perhatikan bahwa EF.Functions.CoalesceUndefined coalesces undefined, bukan null. Untuk menyaring null, gunakan operator C# ?? reguler.

2 Lihat dokumentasi untuk informasi tentang menggunakan pencarian vektor di Azure Cosmos DB, yang bersifat eksperimental. API dapat berubah.