#.net #entity-framework-core
#c# #.net #entity-framework-core
Вопрос:
SQL Server становится очень медленным при пропуске / использовании больших таблиц (> 1000000 строк). Тип ключевого столбца таблиц — Guid, и я знаю последнюю прочитанную строку. Я пытаюсь загрузить следующую страницу, например
var keyGuid = Guid.NewGuid(); // Key Guid of the last read row
// var result1 = DbContext.Entity.Where(x => x.Id > keyGuid).Take(10).ToList();
var result2 = DbContext.Entity.Where(x => x.Id.CompareTo(keyGuid) > 0).Take(10).ToList();
Хотя первый подход не компилируется, второй оценивает запрос на клиенте (QueryClientEvaluationWarning) и тоже бесполезен.
К сожалению, я никак не могу изменить базу данных.
Существует ли какое-либо «собственное» решение EF Core без пользовательского SQL? Может быть, все в порядке, если можно перехватить генерацию кода SQL и разрешить выражение вручную (но как?)
Комментарии:
1. Вы могли бы сами писать SQL-запросы с помощью EF Core, как здесь: docs.microsoft.com/en-us/ef/core/querying/raw-sql или entityframeworktutorial.net/EntityFramework4.3 /…
2. Спасибо, это была бы моя последняя альтернатива. Это универсальный компонент, работающий с любой таблицей, включая некоторые, содержащие составные ключи. Не так просто, потому что требуется немного больше генерации пользовательского кода sql.
3. @Florian ваш код не будет работать без
OrderBy()
предложения. В результатах запроса нет неявного порядка, еслиORDER BY
не указано предложение. Даже еслиID
это кластеризованный индекс, объединения и параллельное выполнение могут изменить способ отображения результатов.
Ответ №1:
EF Core 2.x:
Начиная с версии 2.0, ядро EF поддерживает так называемое отображение скалярных функций базы данных. Это не очень хорошо документировано и обычно используется для отображения некоторой функции базы данных. Но fluent API также позволяет вам предоставлять пользовательский перевод с помощью метода HasTranslation:
Задает обратный вызов, который будет вызываться для выполнения пользовательского преобразования этой функции. Обратный вызов принимает набор выражений, соответствующих параметрам, переданным вызову функции. Обратный вызов должен возвращать выражение, представляющее желаемый перевод.
Следующий класс использует это, определяя несколько пользовательских методов расширения для сравнения Guid
значений и регистрируя для них пользовательский перевод, который преобразует выражения вызова метода в двоичные выражения сравнения, в основном имитируя отсутствующие >
операторы , >=
, <
и <=
Guid, что позволяет переводить их в SQL и корректно выполнять на стороне сервера, как только база данныхподдерживает их (SQLServer делает).
Вот реализация:
public static class GuidFunctions
{
public static bool IsGreaterThan(this Guid left, Guid right) => left.CompareTo(right) > 0;
public static bool IsGreaterThanOrEqual(this Guid left, Guid right) => left.CompareTo(right) >= 0;
public static bool IsLessThan(this Guid left, Guid right) => left.CompareTo(right) < 0;
public static bool IsLessThanOrEqual(this Guid left, Guid right) => left.CompareTo(right) <= 0;
public static void Register(ModelBuilder modelBuilder)
{
RegisterFunction(modelBuilder, nameof(IsGreaterThan), ExpressionType.GreaterThan);
RegisterFunction(modelBuilder, nameof(IsGreaterThanOrEqual), ExpressionType.GreaterThanOrEqual);
RegisterFunction(modelBuilder, nameof(IsLessThan), ExpressionType.LessThan);
RegisterFunction(modelBuilder, nameof(IsLessThanOrEqual), ExpressionType.LessThanOrEqual);
}
static void RegisterFunction(ModelBuilder modelBuilder, string name, ExpressionType type)
{
var method = typeof(GuidFunctions).GetMethod(name, new[] { typeof(Guid), typeof(Guid) });
modelBuilder.HasDbFunction(method).HasTranslation(parameters =>
{
var left = parameters.ElementAt(0);
var right = parameters.ElementAt(1);
return Expression.MakeBinary(type, left, right, false, method);
});
}
}
Все, что вам нужно, это добавить следующую строку в переопределение контекста OnModelCreating
:
GuidFunctions.Register(modelBuilder);
а затем просто используйте их в своих запросах:
var result = DbContext.Entity
.Where(x => x.Id.IsGreaterThan(keyGuid))
.Take(10).ToList();
EF Core 3.0:
HasTranslation
теперь получает и возвращает SqlExpression
экземпляры, поэтому
return Expression.MakeBinary(type, left, right, false, method);
следует заменить на
return new SqlBinaryExpression(type, left, right, typeof(bool), null);