Abbiamo una colonna in cui i dati JSON sono archiviati come una stringa. Questi dati JSON vengono letti e convertiti attraverso la materializzazione in un IDictionary<string, object>
. Tutto funziona bene finché non voglio filtrarlo. Il filtro viene applicato solo dopo aver ottenuto i dati dal database. Avremo milioni di record, quindi questo non è accettabile. Il mio filtro viene completamente ignorato come clausola WHERE da EF Core, ovviamente perché probabilmente non ha idea di come analizzare MethodCallExpressions.
Sto cercando un modo per avvicinarmi il più possibile alla query SQL che ho sotto con l'albero delle espressioni che ho.
Devo convertire questo:
.Call System.Linq.Queryable.Where(
.Constant<QueryTranslator`1[Setting]>(QueryTranslator`1[Setting]),
'(.Lambda #Lambda1<System.Func`2[Setting,System.Boolean]>))
.Lambda #Lambda1<System.Func`2[Setting,System.Boolean]>(Setting $$it)
{
((System.Nullable`1[System.Int32]).If (
$$it.Value != null && .Call ($$it.Value).ContainsKey("Name")
) {
($$it.Value).Item["Name"]
} .Else {
null
} > (System.Nullable`1[System.Int32]).Constant<Microsoft.AspNet.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.Int32]>(Microsoft.AspNet.OData.Query.Expressions.LinqParameterContainer+TypedLinqParameterContainer`1[System.Int32]).TypedProperty)
== .Constant<System.Nullable`1[System.Boolean]>(True)
}
In questo:
SELECT *
FROM [Setting]
WHERE JSON_VALUE([Value], 'lax $.Name') > 1; -- [Value_Name] > 1 is also fine
Con un ExpressionVisitor
sono riuscito ad avvicinarmi come WHERE [Value] = 'Something' ma questo funziona solo per le stringhe e manca il nome della chiave.
Fino a quando non ottiene il supporto "ufficiale", è possibile mappare JSON_VALUE
utilizzando il mapping delle funzioni scalari del database introdotto da EF Core 2.0.
Ad esempio, aggiungi il seguente metodo statico all'interno della tua classe derivata dal contesto o in una classe statica separata come di seguito:
public static class MyDbFunctions
{
[DbFunction("JSON_VALUE", "")]
public static string JsonValue(string source, string path) => throw new NotSupportedException();
}
e se è in una classe separata, aggiungi quanto segue al tuo contesto OnModelCreating
override (non necessario se il metodo è nel contesto):
modelBuilder.HasDbFunction(() => MyDbFunctions.JsonValue(default(string), default(string)));
Ora puoi utilizzarlo all'interno di LINQ per le query Entità simili a EF.Functions
. Basta notare che la funzione restituisce string
, quindi per ingannare il compilatore per "lanciarlo" su valori numerici, puoi usare la tecnica del doppio cast mostrata di seguito (testata e funzionante in EF Core 2.1.2):
var query = db.Set<Setting>()
.Where(s => (int)(object)MyDbFunctions.JsonValue(s.Value, "lax $.Name") > 1);
che si traduce nel desiderato
WHERE JSON_VALUE([Value], 'lax $.Name') > 1
Un altro modo (probabilmente di tipo più sicuro) per eseguire la conversione consiste nell'utilizzare i metodi della classe Convert
(sorprendentemente supportati dal provider SqlServer EF Core):
var query = db.Set<Setting>()
.Where(s => Convert.ToInt32(MyDbFunctions.JsonValue(s.Value, "lax $.Name")) > 1);
che si traduce in
WHERE CONVERT(int, JSON_VALUE([Value], 'lax $.Name')) > 1