Sto usando EF e ho una tabella di database che ha un numero di campi di data e ora che vengono compilati come varie operazioni vengono eseguite sul record. Attualmente sto costruendo un sistema di reporting che implica il filtraggio in base a queste date, ma poiché i filtri (è questa data all'interno di un intervallo, ecc ...) hanno lo stesso comportamento su ogni campo, vorrei riutilizzare la mia logica di filtraggio scrivi solo un singolo filtro data e usalo su ogni campo.Come riutilizzare un filtro di campo in LINQ alle entità
Il mio codice di filtraggio iniziale simile a:
DateTime? dateOneIsBefore = null;
DateTime? dateOneIsAfter = null;
DateTime? dateTwoIsBefore = null;
DateTime? dateTwoIsAfter = null;
using (var context = new ReusableDataEntities())
{
IEnumerable<TestItemTable> result = context.TestItemTables
.Where(record => ((!dateOneIsAfter.HasValue
|| record.DateFieldOne > dateOneIsAfter.Value)
&& (!dateOneIsBefore.HasValue
|| record.DateFieldOne < dateOneIsBefore.Value)))
.Where(record => ((!dateTwoIsAfter.HasValue
|| record.DateFieldTwo > dateTwoIsAfter.Value)
&& (!dateTwoIsBefore.HasValue
|| record.DateFieldTwo < dateTwoIsBefore.Value)))
.ToList();
return result;
}
Questo funziona bene, ma preferirei per ridurre il codice duplicato in 'Dove' metodi come l'algoritmo di filtro è la stessa per ogni campo della data.
Quello che preferirei è qualcosa che è simile al seguente (creerò una classe o struct per i valori di filtro successive) dove posso incapsulare l'algoritmo partita usando forse un metodo di estensione:
DateTime? dateOneIsBefore = null;
DateTime? dateOneIsAfter = null;
DateTime? dateTwoIsBefore = null;
DateTime? dateTwoIsAfter = null;
using (var context = new ReusableDataEntities())
{
IEnumerable<TestItemTable> result = context.TestItemTables
.WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter)
.WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter)
.ToList();
return result;
}
Dove il metodo di estensione potrebbe somigliare:
internal static IQueryable<TestItemTable> WhereFilter(this IQueryable<TestItemTable> source, Func<TestItemTable, DateTime> fieldData, DateTime? dateIsBefore, DateTime? dateIsAfter)
{
source = source.Where(record => ((!dateIsAfter.HasValue
|| fieldData.Invoke(record) > dateIsAfter.Value)
&& (!dateIsBefore.HasValue
|| fieldData.Invoke(record) < dateIsBefore.Value)));
return source;
}
Utilizzando il codice di cui sopra, se il mio codice di filtraggio è la seguente:
IEnumerable<TestItemTable> result = context.TestItemTables
.WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter)
.WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter)
.ToList();
ottengo la seguente eccezione:
A first chance exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll
Additional information: LINQ to Entities does not recognize the method 'System.DateTime Invoke(RAC.Scratch.ReusableDataFilter.FrontEnd.TestItemTable)' method, and this method cannot be translated into a store expression.
Il problema che ho è l'uso di Invoke per ottenere il particolare campo di essere interrogato come questa tecnica non risolve bene a SQL, perché se modifico il mio codice di filtraggio per la seguente verrà eseguito senza errori:
IEnumerable<TestItemTable> result = context.TestItemTables
.ToList()
.AsQueryable()
.WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter)
.WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter)
.ToList();
il problema con questo è che il codice (utilizzando ToList sull'intera tabella prima di filtrare con il metodo di estensione) tira nell'intero database e query come oggetti anziché interrogare il database sottostante quindi non è scalea ble.
Ho anche studiato utilizzando PredicateBuilder da Linqkit, ma non è riuscito a trovare un modo per scrivere il codice senza utilizzare il metodo Invoke.
So che esistono tecniche in cui è possibile esprimere parti della query come stringhe che includono nomi di campi, ma preferirei usare un modo più sicuro per scrivere questo codice.
Inoltre, in un mondo ideale potrei riprogettare il database per avere più record di "date" relativi a un singolo record "item", ma non sono libero di modificare lo schema del database in questo modo.
C'è un altro modo per scrivere l'estensione in modo che non usi Invoke, o dovrei affrontare il riutilizzo del mio codice di filtro in un modo diverso?
Grazie, potrei mancare qualcosa di se ... Con un valore di filtro data non nullo (le mie scuse - Ho forse avrebbero dovuto includere questo nel codice di esempio), ad esempio: DateTime? dateOneIsAfter = new DateTime (2000, 12, 31); ottengo l'eccezione: un'eccezione non gestita di tipo 'System.InvalidCastException' in ReusableDataFilter.FrontEnd.exe Ulteriori informazioni: Impossibile eseguire il cast oggetto di tipo 'System.Linq.Expressions.UnaryExpression' digitare 'sistema .Linq.Expressions.MemberExpression'. sulla riga: var dateFieldName = ((MemberExpression) fieldData.Body) .Member.Name; – NvR
@NvR risposta aggiornata per lavorare con date non annullabili. – Evk
Mentre l'OP si lamentava di questo codice ma dall'eccezione, significa che deve modificare il codice in qualche modo, il 'fieldData' sarebbe qualcosa come' e => e.DateFieldOne' e il suo 'Body' dovrebbe essere sicuramente un' MemberExpression' (ma in qualche modo è un 'UnaryExpresion', potrebbe effettivamente usare' e => e.DateFieldOne.Value'). Il tuo codice originale (con 'DateTime?') Dovrebbe funzionare per 'e => DateFieldOne', il codice corrente dovrebbe funzionare per' e => e.DateFieldOne.Value'. Quindi vorrei +1 per lo sforzo. L'OP potrebbe ignorare la possibilità di conoscere Expression. – Hopeless