Używam EF i mieć tabelę bazy danych, która ma wiele pól daty, które są wypełniane, ponieważ różne operacje są wykonywane na rekord. Obecnie tworzę system raportowania, który obejmuje filtrowanie według tych dat, ale ponieważ filtry (jest to data w zakresie, itp.) Mają takie samo zachowanie w każdym polu, chciałbym ponownie użyć mojej logiki filtrowania, więc zapisuj tylko jeden filtr daty i używaj go w każdym polu.Jak ponownie użyć filtru pola w LINQ do encji
Mój początkowy kod filtrowanie wygląda mniej więcej tak:
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;
}
Działa to dobrze, ale wolałbym, aby zmniejszyć kod duplikowane w „gdzie” metodami jak algorytmu filtra jest taka sama dla każdego pola daty.
Co wolałbym coś, co wygląda następująco (będę tworzyć klasy lub struct dla wartości filtrów późniejszych), gdzie mogę hermetyzacji algorytm dopasowania użyciu może metodę rozszerzenia:
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;
}
Gdzie metoda rozszerzenie mogłoby wyglądać następująco:
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;
}
Stosując powyższy kod, jeśli mój kod filtrowanie jest następujący:
IEnumerable<TestItemTable> result = context.TestItemTables
.WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter)
.WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter)
.ToList();
otrzymuję następujący wyjątek:
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.
Problem mam jest użycie Invoke dostać danej dziedzinie są pytani jak tej techniki nie rozwiązuje ładnie do SQL, ponieważ gdybym zmodyfikować mój kod filtrujący do następujące będzie działać bez błędów:
IEnumerable<TestItemTable> result = context.TestItemTables
.ToList()
.AsQueryable()
.WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter)
.WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter)
.ToList();
problem polega na tym, że kod (używając ToList na całej tabeli przed filtrowanie metodą wydłużania) ciągnie w całej bazy danych i zapytań go jako obiekty zamiast odpytywania bazowa baza danych, więc nie jest skalą ble.
Badałem również użycie PredicateBuilder z Linqkit, ale nie mogłem znaleźć sposobu na napisanie kodu bez użycia metody Invoke.
Wiem, że istnieją techniki, w których można wyrazić części zapytania jako ciągi zawierające nazwy pól, ale wolałbym użyć bezpieczniejszego sposobu pisania tego kodu w bardziej typie.
Również w idealnym świecie mógłbym przeprojektować bazę danych tak, aby zawierała wiele rekordów "daty" związanych z pojedynczym rekordem "produktu", ale nie mam takiej możliwości zmiany schematu bazy danych.
Czy jest inny sposób, muszę napisać rozszerzenie, aby nie używać Invoke, czy też powinienem przeciwdziałać ponownemu wykorzystaniu mojego kodu filtrującego w inny sposób?
Dziękuję, może brakować czegoś chociaż ... Z niepuste wartości daty filtra (moje przeprosiny - I być może to powinno zawierać w kodzie próbki), np: DateTime? dateOneIsAfter = new DateTime (2000, 12, 31); otrzymuję wyjątek: nieobsługiwany wyjątek typu „System.InvalidCastException” wystąpił w ReusableDataFilter.FrontEnd.exe Informacje dodatkowe: Nie można rzutować obiektu typu „System.Linq.Expressions.UnaryExpression” do rodzaju „system .Linq.Expressions.MemberExpression ". na linii: var dateFieldName = ((MemberExpression) fieldData.Body) .Member.Name; – NvR
@NvR zaktualizowano odpowiedź, aby działała z nie-nullowanymi datami. – Evk
Podczas gdy OP narzekali na ten kod, ale z wyjątku, oznacza to, że musi jakoś zmodyfikować kod, "fieldData" będzie czymś w rodzaju 'e => e.DateFieldOne' i jego' Body' powinno być z pewnością 'MemberExpression' (ale w jakiś sposób jest to "UnaryExpresion", może on faktycznie użyć 'e => e.DateFieldOne.Value'). Twój oryginalny kod (z 'DateTime?') Powinien działać dla 'e => DateFieldOne', bieżący kod powinien działać dla' e => e.DateFieldOne.Value'. Więc dałbym +1 za trud. PO może ominąć szansę poznania Wyrażenia. – Hopeless