Запрос с помощью поставщика EF Core Azure Cosmos DB
Основы запроса
Запросы EF Core LINQ можно выполнять в Azure Cosmos DB так же, как и для других поставщиков баз данных. Рассмотрим пример.
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();
Примечание.
Поставщик Azure Cosmos DB не преобразует тот же набор запросов LINQ, что и другие поставщики.
Например, оператор EF Include()
не поддерживается в Azure Cosmos DB, так как запросы между документами не поддерживаются в базе данных.
Ключи секции
Преимущество секционирования заключается в том, чтобы запросы выполнялись только в разделе, где найдены соответствующие данные, экономия затрат и обеспечение ускорения скорости результатов. Запросы, которые не указывают ключи секций, выполняются во всех секциях, что может быть довольно дорогостоящим.
Начиная с EF 9.0 EF автоматически обнаруживает и извлекает сравнения ключей секций в операторах запроса Where
LINQ. Предположим, что мы выполняем следующий запрос к типу сущности Session
, который настроен с помощью ключа иерархической секции:
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();
Изучение журналов, созданных EF, мы видим, что этот запрос выполняется следующим образом:
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"))
В этих журналах мы заметим следующее:
- Первые два сравнения -
TenantId
включено иUserId
- были сняты и отображаются вReadNext
предложении "Секционирование", а не вWHERE
предложении. Это означает, что запрос будет выполняться только в подпартиях для этих значений. SessionId
также является частью иерархического ключа секции, но вместо сравнения равенства он использует оператор больше, чем (>
), и поэтому не может быть снят. Это часть предложения, как любое регулярноеWHERE
свойство.Username
является регулярным свойством , а не частью ключа секции , поэтому он также остается в предложенииWHERE
.
Обратите внимание, что хотя некоторые значения ключа секции не указаны, иерархические ключи секций по-прежнему позволяют использовать только подпартии, соответствующие первым двум свойствам. Хотя это не так эффективно, как нацеливание на одну секцию (как определено всеми тремя свойствами), это все еще гораздо эффективнее, чем назначение всех секций.
Вместо ссылки на свойства ключа секции Where
в операторе можно явно указать их с помощью WithPartitionKey оператора:
var sessions = await context.Sessions
.WithPartitionKey(tenantId, userId)
.Where(e => e.SessionId > 0 && e.Username.Contains("a"))
.ToListAsync();
Это выполняется так же, как и приведенный выше запрос, и может быть предпочтительнее, если вы хотите сделать ключи секции более явными в запросах. Использование WithPartitionKey может потребоваться в версиях EF до 9.0. Следите за журналами, чтобы убедиться, что запросы используют ключи секций должным образом.
Операции точечного чтения
Хотя Azure Cosmos DB позволяет эффективно запрашивать через SQL, такие запросы могут быть довольно дорогостоящими. Azure Cosmos DB также поддерживает операции чтения точек, которые следует использовать при получении одного документа, если известно как свойство, так id
и весь ключ секции. Точка считывает непосредственно определенный документ в определенном разделе и выполняет чрезвычайно эффективно и с сокращением затрат по сравнению с получением того же документа с запросом. Рекомендуется разработать систему, чтобы использовать точки чтения как можно чаще. Дополнительные сведения см. в документации по Azure Cosmos DB.
В предыдущем разделе мы увидели, как EF определяет и извлекает сравнения ключей секций из Where
предложения для более эффективного запроса, ограничивая обработку только соответствующими секциями. Кроме того, можно перейти к шагу и указать id
свойство в запросе. Давайте рассмотрим следующий запрос:
var session = await context.Sessions.SingleAsync(
e => e.Id == someId
&& e.TenantId == tenantId
&& e.UserId == userId
&& e.SessionId == sessionId);
В этом запросе предоставляется значение свойства Id
(которое сопоставляется со свойством Azure Cosmos DB id
), а также значения для всех свойств ключа секции. Кроме того, в запросе нет дополнительных компонентов. При выполнении всех этих условий EF может выполнить запрос в виде точки чтения:
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
Обратите внимание на то, что запрос был выполнен в качестве эффективного считывания точек. Запрос SQL не участвует.
Обратите внимание, что, как и при извлечении ключа секции, в EF 9.0 были сделаны значительные улучшения. Старые версии не надежно обнаруживают и используют операции чтения точек.
Разбиение на страницы
Примечание.
Эта функция появилась в EF Core 9.0 и по-прежнему экспериментальна. Сообщите нам, как это работает для вас, и если у вас есть какие-либо отзывы.
Разбивка на страницы ссылается на получение результатов в страницах, а не все одновременно; Обычно это делается для больших наборов результатов, где отображается пользовательский интерфейс, что позволяет пользователям перемещаться по страницам результатов.
Распространенный способ реализации разбиения на страницы с базами данных — использовать Skip
Take
операторы LINQ (OFFSET
и LIMIT
в SQL). Учитывая размер страницы 10 результатов, третью страницу можно получить с помощью EF Core следующим образом:
var position = 20;
var nextPage = await context.Session
.OrderBy(s => s.Id)
.Skip(position)
.Take(10)
.ToListAsync();
К сожалению, этот метод является довольно неэффективным и может значительно увеличить затраты на запросы. Azure Cosmos DB предоставляет специальный механизм для разбиения на страницы в результате запроса с помощью маркеров продолжения:
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
}
Вместо того, чтобы завершить запрос LINQ с ToListAsync
или аналогичным образом, мы используем ToPageAsync
метод, наставив его получить не более 10 элементов на каждой странице (обратите внимание, что в базе данных может быть меньше элементов). Так как это первый запрос, мы хотели бы получить результаты с самого начала и передать null
в качестве маркера продолжения. ToPageAsync
возвращает маркер CosmosPage
продолжения и значения на странице (до 10 элементов). Программа обычно отправляет эти значения клиенту вместе с маркером продолжения; Это позволит возобновить запрос позже и получить дополнительные результаты.
Предположим, что пользователь теперь нажимает кнопку "Далее" в пользовательском интерфейсе, запрашивая следующие 10 элементов. Затем можно выполнить запрос следующим образом:
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
}
Мы выполняем тот же запрос, но на этот раз мы передаваем маркер продолжения, полученный от первого выполнения; Это указывает обработчику запросов продолжить запрос, в котором он остался, и получить следующие 10 элементов. Когда вы получите последнюю страницу и нет дополнительных результатов, маркер продолжения будет null
иметь значение, и кнопка "Далее" может быть выделена серым цветом. Этот метод разбиения на страницы является чрезвычайно эффективным и экономичным по сравнению с использованием Skip
и Take
.
Дополнительные сведения о разбиении на страницы в Azure Cosmos DB см. на этой странице.
Примечание.
Azure Cosmos DB не поддерживает обратную разбивку на страницы и не предоставляет количество общих страниц или элементов.
ToPageAsync
в настоящее время аннотирован как экспериментальный, так как он может быть заменен более универсальным API разбиения на страницы EF, который не является конкретным для Azure Cosmos DB. Хотя использование текущего API создаст предупреждение о компиляции (EF9102
), это должно быть безопасным. Будущие изменения могут потребовать незначительных настроек в форме API.
FindAsync
FindAsync
— это полезный API для получения сущности по его первичному ключу и предотвращения циклического обхода базы данных, когда сущность уже загружена и отслеживается контекстом.
Разработчики, знакомые с реляционными базами данных, используются для первичного ключа типа сущности, состоящего, например Id
, свойства. При использовании поставщика EF Azure Cosmos DB первичный ключ содержит свойства ключа секции в дополнение к свойству JSON id
. Это связано с тем, что Azure Cosmos DB позволяет разным секциям содержать документы с одним и тем же свойством JSON id
, поэтому только объединенный id
ключ и ключ секции однозначно идентифицирует один документ в контейнере:
public class Session
{
public Guid Id { get; set; }
public string PartitionKey { get; set; }
...
}
var mySession = await context.FindAsync(id, pkey);
Если у вас есть иерархический ключ секции, необходимо передать все значения FindAsync
ключа секции в порядок, в котором они были настроены.
Примечание.
Используйте FindAsync
только в том случае, если сущность уже может отслеживаться контекстом, и вы хотите избежать циклического обхода базы данных.
В противном случае простое использование SingleAsync
— нет разницы в производительности между двумя, когда сущность должна быть загружена из базы данных.
SQL-запросы
Запросы также можно записывать непосредственно в SQL. Например:
var rating = 3;
_ = await context.Blogs
.FromSql($"SELECT VALUE c FROM root c WHERE c.Rating > {rating}")
.ToListAsync();
Этот запрос приводит к следующему выполнению запроса:
SELECT VALUE s
FROM (
SELECT VALUE c FROM root c WHERE c.Angle1 <= @p0
) s
Обратите внимание, что FromSql
было введено в EF 9.0. В предыдущих версиях можно использовать вместо этого, хотя обратите внимание, FromSqlRaw
что этот метод уязвим для атак внедрения sql.
Дополнительные сведения о запросах SQL см. в реляционной документации по запросам SQL. Большая часть этого содержимого также относится к поставщику Azure Cosmos DB.
Сопоставления функций
В этом разделе показано, какие методы и члены .NET преобразуются в функции SQL при запросе с помощью поставщика Azure Cosmos DB.
Функции даты и времени
.NET | SQL | Добавлено в |
---|---|---|
DateTime.UTCNow | GetCurrentDateTime() | |
DateTimeOffset.UTCNow | GetCurrentDateTime() | |
dateTime.Year1 | DateTimePart("гггг", dateTime) | EF Core 9.0 |
dateTimeOffset.Year1 | DateTimePart("гггг", dateTimeOffset) | EF Core 9.0 |
dateTime.AddYears(years)1 | DateTimeAdd("гггг", dateTime) | EF Core 9.0 |
dateTimeOffset.AddYears(years)1 | DateTimeAdd("гггг", dateTimeOffset) | EF Core 9.0 |
1 Другие члены компонента также переводятся (месяц, день...).
Числовые функции
.NET | SQL | Добавлено в |
---|---|---|
двойной. DegreesToRadians(x) | RADIANS(@x) | EF Core 8.0 |
двойной. RadiansToDegrees(x) | DEGREES(@x) | EF Core 8.0 |
ЭФ. 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) |
Совет
Помимо методов, перечисленных здесь, также переводятся соответствующие универсальные математические реализации и методы MathF . Например, Math.Sin
, MathF.Sin
и double.Sin
float.Sin
все сопоставляют функцию SIN
в SQL.
Строковые функции
.NET | SQL | Добавлено в |
---|---|---|
Regex.IsMatch(input, pattern) | RegexMatch(@pattern, @input) | EF Core 7.0 |
Regex.IsMatch(input, pattern, options) | RegexMatch(@input, @pattern, @options) | EF Core 7.0 |
струна. Concat(str0, str1) | @str0 + @str1 | |
струна. Equals(a, b, StringComparison.Ordinal) | STRINGEQUALS(@a, @b) | |
струна. 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) |
Прочие функции
.NET | SQL | Примечания. |
---|---|---|
коллекция. Contains(item) | @item В @collection | |
ЭФ. Functions.CoalesceUndefined(x, y)1 | x ?? г | Добавлено в EF Core 9.0 |
ЭФ. Functions.IsDefined(x) | IS_DEFINED(x) | Добавлено в EF Core 9.0 |
ЭФ. Functions.VectorDistance(vector1, vector2)2 | VectorDistance(vector1, vector2) | Добавлено в EF Core 9.0, экспериментальный |
ЭФ. Functions.VectorDistance(vector1, vector2, bruteForce)2 | VectorDistance(vector1, vector2, bruteForce) | Добавлено в EF Core 9.0, экспериментальный |
ЭФ. Functions.VectorDistance(vector1, vector2, bruteForce, distanceFunction)2 | VectorDistance(vector1, vector2, bruteForce, distanceFunction) | Добавлено в EF Core 9.0, экспериментальный |
1 Обратите внимание, что EF.Functions.CoalesceUndefined
объединения undefined
, а не null
. Для объединения null
используйте обычный оператор C# ??
.
2 См. документацию по использованию векторного поиска в Azure Cosmos DB, которая является экспериментальной. API-интерфейсы могут изменяться.