SQL Server diventa molto lento con Salta / Prendi su tabelle grandi (> 1000000 righe). Il tipo di colonna chiave delle tabelle è Guid e conosco l'ultima riga di lettura. Provo a caricare la pagina successiva come
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();
Mentre il primo approccio non viene compilato, il secondo valuta la query sul client (QueryClientEvaluationWarning) e non è utile.
Sfortunatamente, non posso modificare il database in alcun modo.
Esiste una soluzione EF core "nativa" senza SQL personalizzato? Potrebbe essere ok se è possibile intercettare la generazione del codice SQL e risolvere manualmente l'espressione (ma come?)
EF Core 2.x :
A partire dalla versione 2.0, EF Core supporta la cosiddetta mappatura delle funzioni scalari del database . Non è molto ben documentato e di solito viene utilizzato per mappare alcune funzioni del database. Ma l'API fluente ti consente anche di fornire una traduzione personalizzata tramite il metodo HasTranslation :
Imposta un callback che verrà richiamato per eseguire la traduzione personalizzata di questa funzione. Il callback accetta una raccolta di espressioni corrispondenti ai parametri passati alla chiamata di funzione. Il callback dovrebbe restituire un'espressione che rappresenta la traduzione desiderata.
La seguente classe utilizza che definendo diversi metodi di estensione personalizzati per confrontare i valori Guid
e registra una traduzione personalizzata per loro, che converte le espressioni di chiamata del metodo in espressioni binarie di confronto, simulando sostanzialmente gli operatori mancanti >
, >=
, <
e <=
Guid, che consente di tradurli in SQL ed eseguire correttamente il lato server, non appena il database li supporta (SqlServer lo fa).
Ecco l'implementazione:
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);
});
}
}
Tutto ciò di cui hai bisogno è aggiungere la seguente riga al tuo contesto OnModelCreating
override:
GuidFunctions.Register(modelBuilder);
e poi semplicemente usali nelle tue query:
var result = DbContext.Entity
.Where(x => x.Id.IsGreaterThan(keyGuid))
.Take(10).ToList();
EF Core 3.0 :
HasTranslation
ora riceve e restituisce istanze SqlExpression
, quindi
return Expression.MakeBinary(type, left, right, false, method);
dovrebbe essere sostituito con
return new SqlBinaryExpression(type, left, right, typeof(bool), null);