perché LINQ to SQL è ingannato da un metodo di estensione? e adesso?
-
03-07-2019 - |
Domanda
Considera la mia classe Event
e che conservo DateTime
nel DB come date UTC. Voglio semplicemente restituire un intervallo filtrato in base alla data corrente in un particolare fuso orario - facile vero?
Funziona bene:
IQueryable<Event> test1 = this.GetSortedEvents().Where(e => e.FinishDateTime.Date >= DateTime.UtcNow.Date);
Anche questo funziona bene:
IQueryable<Event> test2 = this.GetSortedEvents().Where(e => e.FinishDateTime.AddHours(3).Date >= DateTime.UtcNow.AddHours(3).Date);
.. e inoltre soddisfa i miei requisiti di fuso orario.
Quindi qui sto pensando di poter spostare questa conversione specifica su questo metodo di estensione:
public static DateTime RiyadhTimeFromUtc(this DateTime utcTime) { return utcTime.AddHours(3); }
NON funziona:
IQueryable<Event> test3 = this.GetSortedEvents().Where(e => e.FinishDateTime.RiyadhTimeFromUtc().Date >= DateTime.UtcNow.RiyadhTimeFromUtc().Date);
.. e ottengo questo NotSupportedException: il metodo 'System.DateTime RiyadhTimeFromUtc (System.DateTime)' non ha una traduzione supportata in SQL.
Questo è ovviamente spazzatura poiché il compilatore l'ha convertito felicemente in SQL quando lo stesso codice NON era nel metodo di estensione.
Ho incontrato " non ha una traduzione supportata in SQL " problema prima con alcuni tipi e più recentemente DateTime. Ma i miei test sopra e questo link dimostrano che il metodo AddHours dovrebbe essere supportato nella traduzione SQL.
Se qualcuno potesse dirmi cosa sto facendo di sbagliato qui (o un approccio diverso a questo problema) sarei davvero grato.
Soluzione
Devi pensarci in termini di un albero delle espressioni, che è il modo in cui Linq-SQL analizza la tua query per trasformarla in SQL.
Quando esamina l'albero vedrà un oggetto DateTime
e quindi verificherà se il metodo chiamato è uno di quelli supportati ( Aggiungi
, AddHours
, ecc.), quindi quando si utilizza direttamente il metodo funziona correttamente.
Quando usi un altro metodo di estensione, non può andare a guardare dentro quel metodo per vedere cosa fa, poiché le informazioni sul corpo di quel metodo non si trovano nell'albero delle espressioni, sono nascoste nell'IL. Quindi non importa se i contenuti del metodo di estensione sono supportati, poiché Linq-to-SQL non è in grado di capire quali siano i contenuti.
Il punto di creare metodi è l'incapsulamento e il nascondimento delle informazioni, che in genere funziona bene nello sviluppo di applicazioni, ma sfortunatamente qui nasconde le informazioni da Linq-a-SQL di cui è necessario essere in grado di vedere le informazioni.
In risposta alla domanda modificata: come risolverlo? Se vuoi mantenere il calcolo della data all'interno dell'espressione Linq, l'unica cosa che puoi fare per mantenere efficiente la query è non usare un metodo di estensione e usare AddHours (3)
direttamente su < codice> DateTime oggetto.
È una delle sfortunate limitazioni di Linq. Come molte cose è un'astrazione un po 'permeabile che, pur fornendo una sintassi comune su una vasta gamma di fonti, ha diverse limitazioni e restrizioni sulle operazioni che il provider per la fonte può / supporterà (per esempio, questo funzionerebbe perfettamente bene in Linq-to -Oggetti in quanto non è necessario tradurre l'albero delle espressioni per eseguirlo).
Altri suggerimenti
Sembra che LINQ-to-SQL sia in grado di capire che RiyadhTimeFromUtc
era davvero solo una chiamata a AddHours (3)
avrebbe dovuto fare un po ' analisi piuttosto sofisticate del tuo codice. Di che hai scritto:
public static DateTime RiyadhTimeFromUtc(this DateTime utcTime)
{
if (Random.GetInt() < 99) {
return utcTime.AddHours(3);
} else {
return utcTime.AddDays(5);
}
}
Come lo tradurrebbe in SQL? Vedi questa discussione per ulteriori informazioni.