Abfragen mit dem EF Core Azure Cosmos DB-Anbieter
Abfragen von Grundlagen
EF Core-LINQ-Abfragen können auf die gleiche Weise wie bei anderen Datenbankanbietern für Azure Cosmos DB ausgeführt werden. Zum Beispiel:
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();
Hinweis
Der Azure Cosmos DB-Anbieter übersetzt nicht die gleichen LINQ-Abfragen wie andere Anbieter.
Beispielsweise wird der EF-Operator Include()
für Azure Cosmos DB nicht unterstützt, da dokumentübergreifende Abfragen in der Datenbank nicht unterstützt werden.
Partitionsschlüssel
Der Vorteil der Partitionierung besteht darin, dass Ihre Abfragen nur für die Partition ausgeführt werden, auf der die relevanten Daten gefunden werden, Kosten sparen und eine schnellere Ergebnisgeschwindigkeit gewährleisten. Abfragen, die keine Partitionsschlüssel angeben, werden auf allen Partitionen ausgeführt, was sehr kostspielig sein kann.
Ab EF 9.0 erkennt und extrahiert EF automatisch Partitionsschlüsselvergleiche in den LINQ-Abfrageoperatoren Where
. Angenommen, wir führen die folgende Abfrage für unseren Session
Entitätstyp aus, der mit einem hierarchischen Partitionsschlüssel konfiguriert ist:
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();
Wenn Sie die von EF generierten Protokolle untersuchen, wird diese Abfrage wie folgt ausgeführt:
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"))
In diesen Protokollen stellen wir Folgendes fest:
- Die ersten beiden Vergleiche – auf
TenantId
undUserId
– wurden aufgehoben und in derReadNext
„Partition” statt in derWHERE
Klausel angezeigt. Dies bedeutet, dass die Abfrage nur für die Unterpartitionen für diese Werte ausgeführt wird. SessionId
ist auch Teil des hierarchischen Partitionsschlüssels, aber anstelle eines Gleichheitsvergleichs wird ein Größer-als-Operator (>
) verwendet und kann daher nicht aufgehoben werden. Sie ist Teil derWHERE
Klausel wie jede normale Eigenschaft.Username
ist eine normale Eigenschaft – nicht Teil des Partitionsschlüssels – und bleibt daher auch in derWHERE
Klausel enthalten.
Beachten Sie, dass auch wenn einige der Partitionsschlüsselwerte nicht bereitgestellt werden, hierarchische Partitionsschlüssel weiterhin nur die Unterpartitionen zulassen, die den ersten beiden Eigenschaften entsprechen. Dies ist zwar nicht so effizient wie das Ziel einer einzelnen Partition (wie durch alle drei Eigenschaften identifiziert), aber es ist immer noch viel effizienter als für alle Partitionen.
Anstatt auf Partitionsschlüsseleigenschaften in einem Where
Operator zu verweisen, können Sie diese explizit mithilfe des WithPartitionKey Operators angeben:
var sessions = await context.Sessions
.WithPartitionKey(tenantId, userId)
.Where(e => e.SessionId > 0 && e.Username.Contains("a"))
.ToListAsync();
Dies wird auf die gleiche Weise wie die oben genannte Abfrage ausgeführt und kann bevorzugt werden, wenn Sie Partitionsschlüssel in Ihren Abfragen expliziter gestalten möchten. Die Verwendung WithPartitionKey kann in Versionen von EF vor 9.0 erforderlich sein – behalten Sie die Protokolle im Auge, um sicherzustellen, dass Abfragen Partitionsschlüssel wie erwartet verwenden.
Punktlesevorgänge
Während Azure Cosmos DB leistungsstarke Abfragen über SQL ermöglicht, können solche Abfragen recht teuer sein. Azure Cosmos DB unterstützt auch Punktlesevorgänge. Diese sollten beim Abrufen eines einzelnen Dokuments verwendet werden, wenn sowohl die Eigenschaft id
als auch der gesamte Partitionsschlüssel bekannt ist. Punktlesevorgänge identifizieren direkt ein bestimmtes Dokument in einer bestimmten Partition, und ihre Ausführung ist äußerst effizient und kostengünstig (verglichen mit dem Abrufen des gleichen Dokuments mit einer Abfrage). Es wird empfohlen, Ihr System so zu gestalten, dass möglichst oft Punktlesevorgänge verwendet werden. Weitere Informationen finden Sie in der Dokumentation zu Azure Cosmos DB.
Im vorherigen Abschnitt sahen wir, dass EF Partitionsschlüsselvergleiche aus der Where
Klausel für eine effizientere Abfrage identifiziert und extrahiert hat, wobei die Verarbeitung nur auf die relevanten Partitionen beschränkt wird. Es ist möglich, einen Schritt weiter zu gehen und die id
Eigenschaft auch in der Abfrage bereitzustellen. Schauen wir uns die folgende Abfrage an:
var session = await context.Sessions.SingleAsync(
e => e.Id == someId
&& e.TenantId == tenantId
&& e.UserId == userId
&& e.SessionId == sessionId);
In dieser Abfrage werden ein Wert für die Eigenschaft Id
(die der Azure Cosmos DB-Eigenschaft id
zugeordnet ist) sowie Werte für alle Partitionsschlüsseleigenschaften bereitgestellt. Darüber hinaus gibt es keine zusätzlichen Komponenten für die Abfrage. Wenn alle diese Bedingungen erfüllt sind, kann EF die Abfrage als Punkt lesen:
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]'
Beachten Sie das ReadItem
, was angibt, dass die Abfrage als effizienter Punktlesevorgang ausgeführt wurde – es ist keine SQL-Abfrage beteiligt.
Beachten Sie, dass wie bei der Partitionsschlüsselextraktion erhebliche Verbesserungen an diesem Mechanismus in EF 9.0 vorgenommen wurden; Ältere Versionen erkennen und verwenden Punktlesevorgänge nicht zuverlässig.
Paginierung
Hinweis
Dieses Feature wurde in EF Core 9.0 eingeführt und ist noch experimentell. Bitte lassen Sie uns wissen, wie es bei Ihnen funktioniert und ob Sie ein Feedback haben.
Die Paginierung bezieht sich auf den seitenweisen Abruf von Ergebnissen und nicht auf alle auf einmal. Dies ist in der Regel bei großen Resultsets der Fall, bei denen eine Benutzeroberfläche angezeigt wird, über die Benutzer durch die Seiten der Ergebnisse navigieren können.
Eine gängige Methode zum Implementieren der Paginierung mit Datenbanken ist die Verwendung der Skip
und Take
LINQ-Operatoren (OFFSET
und LIMIT
in SQL). Bei einer Seitengröße von zehn Ergebnissen kann die dritte Seite wie folgt mit EF Core abgerufen werden:
var position = 20;
var nextPage = context.Session
.OrderBy(s => s.Id)
.Skip(position)
.Take(10)
.ToList();
Leider ist diese Technik ziemlich ineffizient und kann die Abfragekosten erheblich erhöhen. Azure Cosmos DB bietet einen speziellen Mechanismus zum Paginieren anhand des Ergebnisses einer Abfrage, und zwar über die Verwendung von Fortsetzungstoken:
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
}
Anstatt die LINQ-Abfrage mit ToListAsync
oder ähnlich zu beenden, verwenden wir die ToPageAsync
Methode, um maximal 10 Elemente auf jeder Seite abzurufen (beachten Sie, dass es möglicherweise weniger Elemente in der Datenbank gibt). Da dies unsere erste Abfrage ist, möchten wir Ergebnisse von Anfang an abrufen und null
als Fortsetzungstoken übergeben. ToPageAsync
gibt ein CosmosPage
zurück, das ein Fortsetzungstoken und die Werte auf der Seite (bis zu 10 Elemente) verfügbar macht. Ihr Programm sendet diese Werte in der Regel zusammen mit dem Fortsetzungstoken an den Client. Dadurch wird die Abfrage später fortgesetzt und weitere Ergebnisse abgerufen.
Angenommen, der Benutzer klickt jetzt auf die Schaltfläche „Weiter” in der Benutzeroberfläche und fragt nach den nächsten 10 Elementen. Anschließend können Sie die Query wie folgt ausführen:
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
}
Wir führen die gleiche Abfrage aus, übergeben diesmal aber das Fortsetzungstoken aus der ersten Ausführung. Dadurch wird die Abfrage-Engine angewiesen, die Abfrage an der Stelle fortzusetzen, an der sie unterbrochen wurde, und die nächsten zehn Elemente abzurufen. Sobald die letzte Seite abgerufen wurde und keine weiteren Ergebnisse vorliegen, wird das Fortsetzungstoken null
angezeigt, und die Schaltfläche „Weiter” kann abgeblendet werden. Diese Paginierungsmethode ist im Vergleich zur Verwendung von Skip
und Take
äußerst effizient und kostengünstig.
Weitere Informationen zur Paginierung in Azure Cosmos DB finden Sie auf dieser Seite.
Hinweis
Azure Cosmos DB unterstützt keine Rückwärtspaginierung und bietet keine Zählung der Gesamtseiten oder Elemente.
ToPageAsync
ist derzeit als experimentell gekennzeichnet und wird möglicherweise noch durch eine generischere EF-Paginierungs-API ersetzt, die nicht Azure Cosmos DB-spezifisch ist. Obwohl die Verwendung der aktuellen API eine Kompilierungswarnung generiert (EF9102
), sollte dies sicher sein – zukünftige Änderungen erfordern möglicherweise geringfügige Optimierungen im API-Shape.
FindAsync
FindAsync
ist eine nützliche API zum Abrufen einer Entität durch den Primärschlüssel und das Vermeiden eines Datenbank-Roundtrips, wenn die Entität bereits geladen wurde und vom Kontext nachverfolgt wird.
Entwickler, die mit relationalen Datenbanken vertraut sind, werden für den Primärschlüssel eines Entitätstyps verwendet, der z. B. aus einer Id
Eigenschaft besteht. Bei Verwendung des EF-Anbieters von Azure Cosmos DB enthält der Primärschlüssel zusätzlich zu der Eigenschaft, die der JSON-Eigenschaft id
zugeordnet ist, die Partitionsschlüsseleigenschaften. Dies ist der Fall, da bei Azure Cosmos DB unterschiedliche Partitionen Dokumente mit der gleichen JSON-Eigenschaft id
enthalten können und somit nur eine Kombination aus id
und Partitionsschlüssel ein einzelnes Dokument in einem Container eindeutig identifiziert:
public class Session
{
public Guid Id { get; set; }
public string PartitionKey { get; set; }
...
}
var mySession = await context.FindAsync(id, pkey);
Wenn Sie einen hierarchischen Partitionsschlüssel haben, müssen Sie alle Partitionsschlüsselwerte an FindAsync
in der Reihenfolge übergeben, in der sie konfiguriert wurden.
Hinweis
Verwenden Sie FindAsync
nur, wenn die Entität möglicherweise bereits vom Kontext nachverfolgt wird und Sie das Roundtrip der Datenbank vermeiden möchten.
Andernfalls verwenden SingleAsync
Sie einfach – es gibt keinen Leistungsunterschied zwischen den beiden, wenn die Entität aus der Datenbank geladen werden muss.
SQL-Abfragen
Abfragen können auch direkt in SQL geschrieben werden. Zum Beispiel:
var rating = 3;
_ = await context.Blogs
.FromSql($"SELECT VALUE c FROM root c WHERE c.Rating > {rating}")
.ToListAsync();
Diese Abfrage führt zur folgenden Abfrageausführung:
SELECT VALUE s
FROM (
SELECT VALUE c FROM root c WHERE c.Angle1 <= @p0
) s
Beachten Sie, dass FromSql
in EF 9.0 eingeführt wurde. In früheren Versionen kann FromSqlRaw
stattdessen verwendet werden, obwohl beachten Sie, dass diese Methode anfällig für SQL-Einfügungsangriffe ist.
Weitere Informationen zu SQL-Abfragen finden Sie in der relationalen Dokumentation zu SQL-Abfragen. Die meisten dieser Inhalte sind auch für den Azure Cosmos DB-Anbieter relevant.
Funktionszuordnungen
In diesem Abschnitt wird gezeigt, welche .NET-Methoden und -Member bei Abfragen mit dem Azure Cosmos DB-Anbieter in welche SQL-Funktionen übersetzt werden.
Datums- und Uhrzeitfunktionen
.NET | SQL | Hinzugefügt in |
---|---|---|
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 Die anderen Komponentenmitglieder werden ebenfalls übersetzt (Monat, Tag...).
Numeric-Funktionen
.NET | SQL | Hinzugefügt in |
---|---|---|
double.DegreesToRadians(x) | RADIANS(@x) | EF Core 8.0 |
double.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) |
Tipp
Zusätzlich zu den hier aufgeführten Methoden werden auch entsprechende generische mathematische Implementierungen und MathF-Methoden übersetzt. Beispiel: Math.Sin
, MathF.Sin
, double.Sin
und float.Sin
alle Zuordnungen zur SIN
-Funktion in SQL.
Zeichenfolgenfunktionen
.NET | SQL | Hinzugefügt in |
---|---|---|
Regex.IsMatch(input, pattern) | RegexMatch(@pattern, @input) | EF Core 7.0 |
Regex.IsMatch(input, pattern, options) | RegexMatch(@input, @pattern, @options) | EF Core 7.0 |
string.Concat(str0, str1) | @str0 + @str1 | |
string.Equals(a, b, StringComparison.Ordinal) | STRINGEQUALS(@a, @b) | |
string.Equals(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) |
Sonstige Funktionen
.NET | SQL | Hinweise |
---|---|---|
collection.Contains(item) | @item IN @collection | |
EF.Functions.CoalesceUndefined(x, y)1 | x ?? Y | Hinzugefügt in EF Core 9.0 |
EF.Functions.IsDefined(x) | IS_DEFINED(x) | Hinzugefügt in EF Core 9.0 |
EF.Functions.VectorDistance(vector1, vector2)2 | VectorDistance(vector1, vector2) | Hinzugefügt in EF Core 9.0, Experimental |
EF.Functions.VectorDistance(vector1, vector2, bruteForce)2 | VectorDistance(vector1, vector2, bruteForce) | Hinzugefügt in EF Core 9.0, Experimental |
EF.Functions.VectorDistance(vector1, vector2, bruteForce, distanceFunction)2 | VectorDistance(vector1, vector2, bruteForce, distanceFunction) | Hinzugefügt in EF Core 9.0, Experimental |
1 Beachten Sie, dass EF.Functions.CoalesceUndefined
zusammenführt undefined
, nicht null
. Verwenden Sie zum Zusammenführen null
den regulären C# ??
Operator.
2 Informationen zur Verwendung der (experimentellen) Vektorsuche in Azure Cosmos DB finden Sie in der Dokumentation. Alle APIs können sich ändern.