Wykonywanie zapytań za pomocą dostawcy usługi Azure Cosmos DB platformy EF Core
Podstawowe informacje dotyczące wykonywania zapytań
Zapytania EF Core LINQ można wykonywać względem usługi Azure Cosmos DB w taki sam sposób, jak w przypadku innych dostawców baz danych. Na przykład:
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();
Uwaga
Dostawca usługi Azure Cosmos DB nie tłumaczy tego samego zestawu zapytań LINQ co inni dostawcy.
Na przykład operator EF Include()
nie jest obsługiwany w usłudze Azure Cosmos DB, ponieważ zapytania między dokumentami nie są obsługiwane w bazie danych.
Klucze partycji
Zaletą partycjonowania jest wykonywanie zapytań tylko względem partycji, w której znajdują się odpowiednie dane, co pozwala zaoszczędzić koszty i przyspieszyć szybkość wyników. Zapytania, które nie określają kluczy partycji są wykonywane na wszystkich partycjach, co może być dość kosztowne.
Począwszy od programu EF 9.0, program EF automatycznie wykrywa i wyodrębnia porównania kluczy partycji w operatorach zapytania Where
LINQ. Załóżmy, że wykonamy następujące zapytanie względem typu Session
jednostki skonfigurowanego przy użyciu klucza partycji hierarchicznej:
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();
Sprawdzając dzienniki wygenerowane przez platformę EF, widzimy, że to zapytanie zostało wykonane w następujący sposób:
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"))
W tych dziennikach zauważymy następujące kwestie:
- Pierwsze dwa porównania — włączone
TenantId
iUserId
— zostały zniesione i pojawią się wReadNext
klauzuli "Partycja", a nie wWHERE
klauzuli . Oznacza to, że zapytanie będzie wykonywane tylko na częściach podrzędnych dla tych wartości. SessionId
jest również częścią hierarchicznego klucza partycji, ale zamiast porównania równości używa operatora większego niż (>
), dlatego nie można go podnieść. Jest to część klauzuli,WHERE
podobnie jak każda zwykła właściwość.Username
jest zwykłą właściwością , a nie częścią klucza partycji , a zatem pozostaje również w klauzuliWHERE
.
Należy pamiętać, że mimo że niektóre wartości klucza partycji nie są podane, hierarchiczne klucze partycji nadal zezwalają na określanie wartości docelowych tylko części podrzędnych odpowiadających dwóm pierwszym właściwościom. Chociaż nie jest to tak wydajne, jak kierowanie do pojedynczej partycji (zidentyfikowanej przez wszystkie trzy właściwości), nadal jest o wiele bardziej wydajne niż kierowanie wszystkich partycji.
Zamiast odwoływać się do właściwości klucza partycji w Where
operatorze, można je jawnie określić za pomocą WithPartitionKey operatora :
var sessions = await context.Sessions
.WithPartitionKey(tenantId, userId)
.Where(e => e.SessionId > 0 && e.Username.Contains("a"))
.ToListAsync();
Jest to wykonywane w taki sam sposób jak powyższe zapytanie i może być preferowane, jeśli chcesz, aby klucze partycji bardziej jawne w zapytaniach. Użycie WithPartitionKey narzędzia może być konieczne w wersjach programu EF przed wersją 9.0 — pamiętaj o dziennikach, aby upewnić się, że zapytania używają kluczy partycji zgodnie z oczekiwaniami.
Odczyty punktów
Chociaż usługa Azure Cosmos DB umożliwia zaawansowane wykonywanie zapytań za pośrednictwem języka SQL, takie zapytania mogą być dość kosztowne. Usługa Azure Cosmos DB obsługuje również odczyty punktów, które powinny być używane podczas pobierania pojedynczego dokumentu, jeśli jest znana zarówno id
właściwość, jak i cały klucz partycji. Punkt odczytuje bezpośrednio zidentyfikować konkretny dokument w określonej partycji i wykonać niezwykle wydajnie i przy obniżonych kosztach w porównaniu z pobieraniem tego samego dokumentu za pomocą zapytania. Zaleca się zaprojektowanie systemu tak często, aby korzystać z odczytów punktów. Aby dowiedzieć się więcej, zobacz dokumentację usługi Azure Cosmos DB.
W poprzedniej sekcji widzieliśmy ef identyfikowanie i wyodrębnianie porównań kluczy partycji z Where
klauzuli w celu bardziej wydajnego wykonywania zapytań, ograniczając przetwarzanie tylko do odpowiednich partycji. Można przejść krok dalej i podać id
właściwość w zapytaniu. Przeanalizujmy następujące zapytanie:
var session = await context.Sessions.SingleAsync(
e => e.Id == someId
&& e.TenantId == tenantId
&& e.UserId == userId
&& e.SessionId == sessionId);
W tym zapytaniu zostanie podana wartość Id
właściwości (która jest mapowana na właściwość usługi Azure Cosmos DB id
), a także wartości dla wszystkich właściwości klucza partycji. Ponadto w zapytaniu nie ma żadnych dodatkowych składników. Po spełnieniu wszystkich tych warunków program EF może wykonać zapytanie jako odczyt punktu:
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]'
Zwróć uwagę na element ReadItem
, który wskazuje, że zapytanie zostało wykonane jako wydajny odczyt punktu — żadne zapytanie SQL nie jest zaangażowane.
Należy pamiętać, że podobnie jak w przypadku wyodrębniania klucza partycji wprowadzono znaczące ulepszenia tego mechanizmu w programie EF 9.0; starsze wersje nie wykrywają niezawodnie odczytów punktów i używają ich.
Podział na strony
Uwaga
Ta funkcja została wprowadzona w programie EF Core 9.0 i jest nadal eksperymentalna. Poinformuj nas, jak to działa dla Ciebie i jeśli masz jakieś opinie.
Stronicowanie odnosi się do pobierania wyników na stronach, a nie wszystkich jednocześnie; Zazwyczaj jest to wykonywane w przypadku dużych zestawów wyników, w których jest wyświetlany interfejs użytkownika, co umożliwia użytkownikom nawigowanie po stronach wyników.
Typowym sposobem implementacji stronicowania z bazami danych jest użycie Skip
Take
operatorów LINQ (OFFSET
i LIMIT
w języku SQL). Biorąc pod uwagę rozmiar strony 10 wyników, trzecią stronę można pobrać za pomocą programu EF Core w następujący sposób:
var position = 20;
var nextPage = context.Session
.OrderBy(s => s.Id)
.Skip(position)
.Take(10)
.ToList();
Niestety, ta technika jest dość nieefektywna i może znacznie zwiększyć koszty zapytań. Usługa Azure Cosmos DB udostępnia specjalny mechanizm stronicowania za pośrednictwem wyniku zapytania przy użyciu tokenów kontynuacji:
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
}
Zamiast przerywać zapytanie LINQ za ToListAsync
pomocą metody lub podobnej, używamy ToPageAsync
metody , nakazując jej uzyskanie maksymalnie 10 elementów na każdej stronie (należy pamiętać, że w bazie danych może znajdować się mniej elementów). Ponieważ jest to nasze pierwsze zapytanie, chcemy uzyskać wyniki od początku i przekazać null
jako token kontynuacji. ToPageAsync
Zwraca element CosmosPage
, który uwidacznia token kontynuacji i wartości na stronie (maksymalnie 10 elementów). Program zazwyczaj wysyła te wartości do klienta wraz z tokenem kontynuacji; pozwoli to wznowić zapytanie później i pobrać więcej wyników.
Załóżmy, że użytkownik kliknie teraz przycisk "Dalej" w interfejsie użytkownika z prośbą o kolejne 10 elementów. Następnie możesz wykonać zapytanie w następujący sposób:
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
}
Wykonamy to samo zapytanie, ale tym razem przekazujemy token kontynuacji otrzymany od pierwszego wykonania; Spowoduje to, że aparat zapytań będzie kontynuować zapytanie, w którym zostało przerwane, i pobrać kolejne 10 elementów. Po pobraniu ostatniej strony i nie ma więcej wyników, token kontynuacji będzie mieć null
wartość , a przycisk "Dalej" może być wyszarzony. Ta metoda stronicowania jest niezwykle wydajna i ekonomiczna w porównaniu z użyciem Skip
i Take
.
Aby dowiedzieć się więcej na temat stronicowania w usłudze Azure Cosmos DB, zobacz tę stronę.
Uwaga
Usługa Azure Cosmos DB nie obsługuje stronicowania wstecznego i nie udostępnia liczby wszystkich stron ani elementów.
ToPageAsync
jest obecnie oznaczona jako eksperymentalna, ponieważ może zostać zastąpiona bardziej ogólnym interfejsem API stronicowania EF, który nie jest specyficzny dla usługi Azure Cosmos DB. Mimo że użycie bieżącego interfejsu API spowoduje wygenerowanie ostrzeżenia o kompilacji (EF9102
), powinno to być bezpieczne — przyszłe zmiany mogą wymagać drobnych poprawek w kształcie interfejsu API.
FindAsync
FindAsync
jest przydatnym interfejsem API do pobierania jednostki przy użyciu klucza podstawowego i unikania roundtrip bazy danych, gdy jednostka została już załadowana i jest śledzona przez kontekst.
Deweloperzy zaznajomieni z relacyjnymi bazami danych są przyzwyczajeni do klucza podstawowego typu jednostki składającego się np. z Id
właściwości. W przypadku korzystania z dostawcy usługi EF Azure Cosmos DB klucz podstawowy zawiera właściwości klucza partycji oprócz właściwości zamapowanej na właściwość JSON id
. Dzieje się tak, ponieważ usługa Azure Cosmos DB umożliwia różnym partycjom zawieranie dokumentów z tą samą właściwością JSON id
, a więc tylko klucz połączony id
i klucz partycji jednoznacznie identyfikują pojedynczy dokument w kontenerze:
public class Session
{
public Guid Id { get; set; }
public string PartitionKey { get; set; }
...
}
var mySession = await context.FindAsync(id, pkey);
Jeśli masz hierarchiczny klucz partycji, musisz przekazać wszystkie wartości klucza partycji do FindAsync
elementu w kolejności, w której zostały skonfigurowane.
Uwaga
Używaj FindAsync
tylko wtedy, gdy jednostka może być już śledzona przez kontekst i chcesz uniknąć dwukierunkowego przejścia bazy danych.
W przeciwnym razie po prostu użyj — SingleAsync
nie ma różnicy w wydajności między nimi, gdy jednostka musi zostać załadowana z bazy danych.
Zapytania SQL
Zapytania można również zapisywać bezpośrednio w języku SQL. Na przykład:
var rating = 3;
_ = await context.Blogs
.FromSql($"SELECT VALUE c FROM root c WHERE c.Rating > {rating}")
.ToListAsync();
To zapytanie powoduje wykonanie następującego zapytania:
SELECT VALUE s
FROM (
SELECT VALUE c FROM root c WHERE c.Angle1 <= @p0
) s
Należy pamiętać, że FromSql
wprowadzono w programie EF 9.0. W poprzednich wersjach można zamiast tego użyć metody , chociaż należy pamiętać, FromSqlRaw
że ta metoda jest podatna na ataki polegających na wstrzyknięciu kodu SQL.
Aby uzyskać więcej informacji na temat wykonywania zapytań SQL, zobacz dokumentację relacyjną dotyczącą zapytań SQL. Większość tej zawartości jest również odpowiednia dla dostawcy usługi Azure Cosmos DB.
Mapowania funkcji
W tej sekcji pokazano, które metody i elementy członkowskie platformy .NET są tłumaczone na funkcje SQL podczas wykonywania zapytań za pomocą dostawcy usługi Azure Cosmos DB.
Funkcje daty i godziny
.NET | SQL | Dodano element w |
---|---|---|
DateTime.UtcNow | GetCurrentDateTime() | |
DateTimeOffset.UtcNow | GetCurrentDateTime() | |
dateTime.Year1 | DateTimePart("yyyy", dateTime) | EF Core 9.0 |
dateTimeOffset.Year1 | 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 Pozostałe składowe są również tłumaczone (Miesiąc, Dzień...).
Funkcje liczbowe
.NET | SQL | Dodano element w |
---|---|---|
podwójny. DegreesToRadians(x) | RADIANS (@x) | EF Core 8.0 |
podwójny. RadiansToDegrees(x) | DEGREES(@x) | EF Core 8.0 |
EF. Functions.Random() | RAND() | |
Math.Abs(wartość) | 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(wartość) | SIGN(@value) | |
Math.Sin(a) | SIN(@a) | |
Math.Sqrt(d) | SQRT(@d) | |
Math.Tan(a) | TAN(@a) | |
Math.Truncate(d) | TRUNC(@d) |
Napiwek
Oprócz metod wymienionych tutaj, odpowiednie ogólne implementacje matematyczne i metody MathF są również tłumaczone. Na przykład , Math.Sin
, MathF.Sin
double.Sin
i float.Sin
wszystkie mapuj SIN
na funkcję w języku SQL.
Funkcje ciągów
.NET | SQL | Dodano element w |
---|---|---|
Regex.IsMatch(input, pattern) | RegexMatch(@pattern, @input) | EF Core 7.0 |
Regex.IsMatch(dane wejściowe, wzorzec, opcje) | RegexMatch(@input, @pattern, @options) | EF Core 7.0 |
struna. Concat(str0, str1) | @str0 + @str1 | |
struna. Equals(a, b, StringComparison.Ordinal) | STRINGEQUALS(@a, @b) | |
struna. Equals(a, b, StringComparison.OrdinalIgnoreCase) | STRINGEQUALS(@a, @b, true) | |
stringValue.Contains(wartość) | 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(wartość) | ENDSWITH(@stringValue, @value) | |
stringValue.EndsWith(value, StringComparison.Ordinal) | ENDSWITH(@stringValue, @value, false) | EF Core 9.0 |
stringValue.EndsWith(wartość, StringComparison.OrdinalIgnoreCase) | ENDSWITH(@stringValue, @value, true) | EF Core 9.0 |
stringValue.Equals(value, StringComparison.Ordinal) | STRINGEQUALS(@stringValue, @value) | |
stringValue.Equals(wartość, StringComparison.OrdinalIgnoreCase) | STRINGEQUALS(@stringValue, @value, true) | |
stringValue.FirstOrDefault() | LEFT(@stringValue, 1) | |
stringValue.IndexOf(wartość) | 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(wartość) | 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) |
Różne funkcje
.NET | SQL | Uwagi |
---|---|---|
kolekcja. Contains(element) | @item W @collection | |
EF. Functions.CoalesceUndefined(x, y)1 | x ?? t | Dodano w programie EF Core 9.0 |
EF. Functions.IsDefined(x) | IS_DEFINED(x) | Dodano w programie EF Core 9.0 |
EF. Functions.VectorDistance(vector1, vector2)2 | VectorDistance(vector1, vector2) | Dodano w programie EF Core 9.0, eksperymentalnym |
EF. Functions.VectorDistance(vector1, vector2, bruteForce)2 | VectorDistance(vector1, vector2, bruteForce) | Dodano w programie EF Core 9.0, eksperymentalnym |
EF. Functions.VectorDistance(vector1, vector2, bruteForce, distanceFunction)2 | VectorDistance(vector1, vector2, bruteForce, distanceFunction) | Dodano w programie EF Core 9.0, eksperymentalnym |
1 Należy pamiętać, że EF.Functions.CoalesceUndefined
łączenie undefined
, a nie null
. Aby połączyć null
element , użyj zwykłego operatora języka C# ??
.
2 Zapoznaj się z dokumentacją dotyczącą korzystania z wyszukiwania wektorowego w usłudze Azure Cosmos DB, która jest eksperymentalna. Interfejsy API mogą ulec zmianie.