Sdílet prostřednictvím


Dotazování pomocí zprostředkovatele AZURE Cosmos DB EF Core

Základy dotazování

Dotazy LINQ EF Core je možné spouštět ve službě Azure Cosmos DB stejným způsobem jako u jiných poskytovatelů databází. Příklad:

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

Poznámka:

Zprostředkovatel Azure Cosmos DB nepřekládá stejnou sadu dotazů LINQ jako ostatní zprostředkovatelé. Operátor EF Include() se například ve službě Azure Cosmos DB nepodporuje, protože dotazy mezi dokumenty nejsou v databázi podporované.

Klíče oddílu

Výhodou dělení je, aby se dotazy spouštěly pouze u oddílu, ve kterém jsou nalezena relevantní data, což šetří náklady a zajišťuje rychlejší rychlost výsledků. Dotazy, které nezadávají klíče oddílů, se spouští ve všech oddílech, což může být poměrně nákladné.

Od EF 9.0 ef automaticky rozpozná a extrahuje porovnání klíčů oddílů v operátorech Where dotazu LINQ. Předpokládejme, že pro náš Session typ entity spustíme následující dotaz, který je nakonfigurovaný s hierarchickým klíčem oddílu:

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

Při zkoumání protokolů generovaných EF vidíme, že se tento dotaz spustil takto:

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

V těchto protokolech si všimneme následujících:

  • První dvě porovnání – zapnutá TenantId a UserId – byla odebrána a zobrazí se v oddílu ReadNext místo v WHERE klauzuli. To znamená, že dotaz se provede pouze u dílčích částí těchto hodnot.
  • SessionId je také součástí hierarchického klíče oddílu, ale místo porovnání rovnosti používá operátor větší než (>), a proto jej nelze zrušit. Je součástí WHERE klauzule jako jakákoli běžná vlastnost.
  • Username je pravidelná vlastnost , nikoli část klíče oddílu , a proto zůstává v WHERE klauzuli.

Všimněte si, že i když nejsou zadané některé hodnoty klíče oddílu, hierarchické klíče oddílů stále umožňují cílení pouze na dílčí oddíly, které odpovídají prvním dvěma vlastnostem. I když to není tak efektivní jako cílení na jeden oddíl (jak jsou identifikovány všemi třemi vlastnostmi), je stále mnohem efektivnější než cílení na všechny oddíly.

Místo odkazování na vlastnosti klíče oddílu v operátoru Where je můžete explicitně zadat pomocí operátoru WithPartitionKey :

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

To se provede stejným způsobem jako výše uvedený dotaz a může být vhodnější, pokud chcete, aby klíče oddílů byly explicitnější v dotazech. Použití WithPartitionKey může být nezbytné ve verzích EF starších než 9.0 – sledujte protokoly, abyste zajistili, že dotazy používají klíče oddílů podle očekávání.

Čtení bodů

Azure Cosmos DB sice umožňuje výkonné dotazování prostřednictvím SQL, ale tyto dotazy můžou být poměrně nákladné. Azure Cosmos DB také podporuje čtení bodů, které by se měly použít při načítání jednoho dokumentu, pokud id je známa vlastnost i celý klíč oddílu. Bod čte přímo konkrétní dokument v určitém oddílu a provádí extrémně efektivně a s nižšími náklady ve srovnání s načtením stejného dokumentu pomocí dotazu. Doporučujeme navrhnout systém tak, aby co nejčastěji využíval čtení bodů. Další informace najdete v dokumentaci ke službě Azure Cosmos DB.

V předchozí části jsme viděli, jak EF identifikuje a extrahuje porovnání klíčů oddílů z Where klauzule pro efektivnější dotazování a omezuje zpracování pouze na příslušné oddíly. Dále je možné přejít k dalšímu kroku a zadat id vlastnost také v dotazu. Pojďme se podívat na následující dotaz:

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

V tomto dotazu je k dispozici hodnota vlastnosti Id (která se mapuje na vlastnost Azure Cosmos DB id ) a také hodnoty pro všechny vlastnosti klíče oddílu. Kromě toho dotaz neobsahuje žádné další komponenty. Pokud jsou splněny všechny tyto podmínky, ef dokáže spustit dotaz jako přečtený bod:

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]'

ReadItemVšimněte si , který označuje, že dotaz byl proveden jako efektivní bod čtení – není zapojen žádný dotaz SQL.

Všimněte si, že stejně jako u extrakce klíče oddílu došlo k významným vylepšením tohoto mechanismu v EF 9.0; starší verze spolehlivě nerozpozná a nepoužívají čtení bodů.

Stránkování

Poznámka:

Tato funkce byla představena v EF Core 9.0 a je stále experimentální. Dejte nám prosím vědět, jak to pro vás funguje, a pokud máte zpětnou vazbu.

Stránkování odkazuje na načítání výsledků na stránkách, nikoli najednou; to se obvykle provádí u velkých sad výsledků, kde se zobrazí uživatelské rozhraní, což uživatelům umožňuje procházet stránky výsledků.

Běžným způsobem implementace stránkování s databázemi je použití Skip operátorů LINQ Take (OFFSET a LIMIT v SQL). Vzhledem k velikosti stránky 10 výsledků je možné třetí stránku načíst pomocí EF Core následujícím způsobem:

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

Tato technika je bohužel poměrně neefektivní a může výrazně zvýšit náklady na dotazování. Azure Cosmos DB poskytuje speciální mechanismus pro stránkování prostřednictvím výsledku dotazu prostřednictvím použití tokenů pokračování:

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
}

Místo ukončení dotazu LINQ s podobným dotazem ToListAsync používáme metodu ToPageAsync a dáváme jí pokyn, aby na každé stránce získal maximálně 10 položek (všimněte si, že v databázi může být méně položek). Vzhledem k tomu, že se jedná o náš první dotaz, chceme získat výsledky od začátku a předat null jako token pokračování. ToPageAsyncCosmosPagevrátí hodnotu , která zveřejňuje token pokračování a hodnoty na stránce (až 10 položek). Program obvykle tyto hodnoty odešle klientovi spolu s tokenem pro pokračování; to umožní později obnovit dotaz a načíst další výsledky.

Předpokládejme, že uživatel teď v uživatelském rozhraní klikne na tlačítko Další a požádá o dalších 10 položek. Dotaz pak můžete spustit následujícím způsobem:

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
}

Provedeme stejný dotaz, ale tentokrát předáme token pro pokračování přijatý z prvního spuštění; tím tomuto modulu dáváte pokyn, aby pokračoval v dotazu tam, kde skončil, a načte dalších 10 položek. Jakmile načteme poslední stránku a nebudou k dispozici žádné další výsledky, token pokračování bude nulla tlačítko Další může být neaktivní. Tato metoda stránkování je velmi efektivní a nákladově efektivní ve srovnání s používáním Skip a Take.

Další informace o stránkování ve službě Azure Cosmos DB najdete na této stránce.

Poznámka:

Azure Cosmos DB nepodporuje zpětné stránkování a neposkytuje celkový počet stránek ani položek.

ToPageAsync je momentálně označený jako experimentální, protože se může nahradit obecnějším rozhraním API stránkování EF, které není specifické pro Službu Azure Cosmos DB. I když použijete aktuální rozhraní API, vygeneruje se upozornění kompilace (EF9102), mělo by to být bezpečné – budoucí změny můžou vyžadovat menší úpravy ve tvaru rozhraní API.

FindAsync

FindAsync je užitečné rozhraní API pro získání entity podle primárního klíče a zabránění zaokrouhlování databáze, pokud je entita již načtena a sledována kontextem.

Vývojáři obeznámení s relačními databázemi se používají k primárnímu klíči typu entity, který se skládá například z Id vlastnosti. Při použití zprostředkovatele EF Azure Cosmos DB obsahuje primární klíč kromě vlastnosti namapované na vlastnost JSON id také vlastnosti klíče oddílu. Důvodem je skutečnost, že Azure Cosmos DB umožňuje různým oddílům obsahovat dokumenty se stejnou vlastností JSON id , takže pouze sloučený id klíč a klíč oddílu jednoznačně identifikuje jeden dokument v kontejneru:

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

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

Pokud máte hierarchický klíč oddílu, musíte předat všechny hodnoty FindAsyncklíče oddílu v pořadí, v jakém byly nakonfigurovány.

Poznámka:

Použijte FindAsync pouze v případě, že entita už může být sledována vaším kontextem a chcete se vyhnout zaokrouhlování databáze. V opačném případě jednoduše použijte SingleAsync – mezi nimi není žádný rozdíl v výkonu, když je potřeba entitu načíst z databáze.

Dotazy SQL

Dotazy lze také zapsat přímo v SQL. Příklad:

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

Výsledkem tohoto dotazu je následující spuštění dotazu:

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

Všimněte si, že FromSql byla zavedena v EF 9.0. V předchozích verzích FromSqlRaw je možné místo toho použít, i když si všimněte, že metoda je zranitelná vůči útokům prostřednictvím injektáže SQL.

Další informace o dotazování SQL najdete v relační dokumentaci k dotazům SQL. Většina tohoto obsahu je relevantní i pro poskytovatele služby Azure Cosmos DB.

Mapování funkcí

Tato část ukazuje, které metody a členy .NET se překládají do kterých funkcí SQL při dotazování pomocí zprostředkovatele Azure Cosmos DB.

Funkce pro datum a čas

.NET SQL Přidáno do
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(roky)1 DateTimeAdd("yyyy"; dateTime) EF Core 9.0
dateTimeOffset.AddYears(roky)1 DateTimeAdd("yyyy"; dateTimeOffset) EF Core 9.0

1 Ostatní členové komponenty se přeloží i (měsíc, den...).

Numerické funkce

.NET SQL Přidáno do
dvojitý. DegreesToRadians(x) RADIANS(@x) EF Core 8.0
dvojitý. 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

Kromě zde uvedených metod jsou také přeloženy odpovídající obecné matematické implementace a metody MathF . Například , Math.Sin, MathF.Sindouble.Sina float.Sin všechny mapování na SIN funkci v SQL.

Funkce řetězců

.NET SQL Přidáno do
Regex.IsMatch(input, pattern) RegexMatch(@pattern, @input) EF Core 7.0
Regex.IsMatch(vstup, vzor, možnosti) RegexMatch(@input, @pattern, @options) EF Core 7.0
řetězec. Concat(str0, str1) @str0 + @str1
řetězec. Equals(a, b, StringComparison.Ordinal) STRINGEQUALS(@a, @b)
řetězec. 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(hodnota, 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(hodnota, 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(hodnota, 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ůzné funkce

.NET SQL Notes
sbírka. Contains(item) @item V @collection
EF. Functions.CoalesceUndefined(x, y)1 x ?? y Přidáno v EF Core 9.0
EF. Functions.IsDefined(x) IS_DEFINED(x) Přidáno v EF Core 9.0
EF. Functions.VectorDistance(vector1, vector2)2 VectorDistance(vector1; vector2) Přidáno v EF Core 9.0, Experimentální
EF. Functions.VectorDistance(vector1, vector2, bruteForce)2 VectorDistance(vector1; vector2; bruteForce) Přidáno v EF Core 9.0, Experimentální
EF. Functions.VectorDistance(vector1, vector2, bruteForce, distanceFunction)2 VectorDistance(vector1, vector2, bruteForce, distanceFunction) Přidáno v EF Core 9.0, Experimentální

1 Všimněte si, že EF.Functions.CoalesceUndefined sluhuje undefined, ne null. Ke sjednocení nullpoužijte běžný operátor jazyka C# ?? .

2 V dokumentaci najdete informace o použití vektorového vyhledávání ve službě Azure Cosmos DB, což je experimentální. Rozhraní API se můžou změnit.