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
danUserId
- telah dicabut, dan muncul diReadNext
"Partisi" daripada dalamWHERE
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 bagianWHERE
dari klausul seperti properti biasa.Username
adalah properti reguler - bukan bagian dari kunci partisi - dan oleh karena itu tetap dalamWHERE
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]'
ReadItem
Perhatikan , 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. ToPageAsync
CosmosPage
mengembalikan , 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.Sin
double.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.