Я использую EF и имею таблицу базы данных, которая имеет несколько полей времени даты, которые заполняются, когда в записи выполняются различные операции. В настоящее время я создаю систему отчетности, которая включает фильтрацию по этим датам, но поскольку фильтры (эта дата в пределах диапазона и т. Д.) Имеют одинаковое поведение в каждом поле, я хотел бы повторно использовать свою логику фильтрации, чтобы я пишите только один фильтр даты и используйте его в каждом поле.Как повторно использовать полевой фильтр в LINQ для объектов
Мой исходный код фильтрации выглядит примерно так:
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;
}
Это прекрасно работает, но я бы предпочел, чтобы уменьшить дублирование кода в «где» методы, как алгоритм фильтра одинакова для каждого поля даты.
То, что я предпочел бы что-то, что выглядит следующим образом (я создать класс или структуры для значений фильтра позже), где я могу инкапсулировать алгоритм совпадения с использованием, может быть метод расширения:
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;
}
Где метод расширения может выглядеть примерно так:
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;
}
Используя приведенный выше код, если мой фильтрации код выглядит следующим образом:
IEnumerable<TestItemTable> result = context.TestItemTables
.WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter)
.WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter)
.ToList();
я получаю следующее исключение:
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.
У меня проблемы является использование Invoke, чтобы получить конкретное поле выполняется запрос в этой технике не решает красиво SQL, потому что если я изменить мой код фильтрации для следующий он будет работать без ошибок:
IEnumerable<TestItemTable> result = context.TestItemTables
.ToList()
.AsQueryable()
.WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter)
.WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter)
.ToList();
проблема с этим состоит в том, что код (с помощью ToList на всю таблицу перед фильтрацией с помощью метода расширения) тянет всю базу данных и запросов, которые он в качестве объектов вместо запроса базовая база данных, поэтому она не масштабируется BLE.
Я также изучил использование PredicateBuilder из Linqkit, но не смог найти способ написать код без использования метода Invoke.
Я знаю, что есть методы, где можно выражать части запроса в виде строк, которые включают имена полей, но я бы предпочел использовать более безопасный способ записи этого кода.
Кроме того, в идеальном мире я мог бы переделать базу данных, чтобы иметь несколько записей «даты», связанных с одной записью «item», но я не имею права изменять схему базы данных таким образом.
Есть ли другой способ, чтобы написать расширение, чтобы он не использовал Invoke, или я должен заниматься повторным использованием моего кода фильтрации по-другому?
Спасибо, я мог бы что-то отсутствует, хотя ... с ненулевым значением даты фильтра (мои извинения - я, возможно, следовало бы включить это в примере кода), например: DateTime? dateOneIsAfter = new DateTime (2000, 12, 31); я получаю исключение: Необработанное исключение типа «System.InvalidCastException» произошло в ReusableDataFilter.FrontEnd.exe Дополнительная информация: Не удается привести объект типа «System.Linq.Expressions.UnaryExpression» для типа «System .Linq.Expressions.MemberExpression. на линии: var dateFieldName = ((MemberExpression) fieldData.Body) .Member.Name; – NvR
@NVR обновленный ответ для работы с датами, не допускающими нулевое значение. – Evk
Хотя OP жаловался на этот код, но из Exception, это означает, что он должен каким-то образом изменить код, 'fieldData' будет чем-то вроде' e => e.DateFieldOne', а его 'Body' должен быть, безусловно,' MemberExpression' (но каким-то образом это «UnaryExpresion», он может фактически использовать «e => e.DateFieldOne.Value'). Ваш исходный код (с 'DateTime?') Должен работать для 'e => DateFieldOne', текущий код должен работать для' e => e.DateFieldOne.Value'. Поэтому я бы добавил +1 за усилия. OP может обойти случай, чтобы узнать об Expression. – Hopeless