#c# #.net-core #roslyn #.net-5
Вопрос:
У меня есть класс DateRange, который я хотел бы применить к IQueryable в качестве предиката where, автоматически используя даты начала и окончания и автоматически используя открытый или закрытый интервал.
public class DateRange { public DateTime? BeginDate { get; set; } public DateTime? EndDate { get; set; } public bool BeginInclusive { get; set; } public bool EndInclusive { get; set; } public DateRange() { BeginInclusive = true; EndInclusive = false; } public IQueryablelt;Tgt; Applylt;Tgt;( IQueryablelt;Tgt; source, Expressionlt;Funclt;T,DateTimegt;gt; dateField ) { var result = source; if (BeginDate.HasValue) { if (BeginInclusive) result = result.Where( x =gt; dateField gt;= BeginDate ); //does not compile else result = result.Where( x =gt; dateField gt; BeginDate ); //does not compile } if (EndDate.HasValue) { if (EndInclusive) result = result.Where( x =gt; dateField lt;= EndDate ); //does not compile else result = result.Where( x =gt; dateField lt; EndDate ); //does not compile } return result; } }
И я хочу назвать это так: поле даты-это любое свойство даты и времени T.
DateRange d; IQueryablelt;Tgt; q; q = d.Apply( q, x =gt; x.DateField );
Поэтому я хочу передать выражение элемента в метод Apply и заставить его применить соответствующее предложение where к результирующему набору, но я не могу понять, как получить выражение элемента поля даты, встроенное в выражение предиката where. См.Строки «не компилировать» в классе выше. Мне нужно как-то преобразовать поле даты или построить выражение предиката каким-то другим способом, но я понятия не имею, как это сделать.
Комментарии:
1. Вам придется создавать вручную
dateField gt;= BeginDate
, используяExpression
методы класса.2. Я не знаю, как это сделать. Я не очень много работаю с самим классом выражений. Я использовал конструктор прогнозов LinqKit, но в этой ситуации это не очень помогает. Окончательный предикат » где » также должен работать в Linq-to-Сущностях.
3. Как это выглядит:
var expr = Expression.GreaterThanOrEqual( dateField.Body, Expression.Constant( BeginDate ) ); result = result.Where( Expression.Lambdalt;Funclt;T, boolgt;gt;( expr ) );
4. Я почти уверен, что вам нужно передать параметры
Expression.Lambda
. Ваш код выдает исключение, когда я пытаюсь это сделать. Проверьте мой ответ.
Ответ №1:
То, что вы хотите здесь сделать, — это составить выражения; вы пытаетесь применить одно выражение к результату другого. Вы действительно можете написать метод для этого:
public static Expressionlt;Funclt;TSource, TResultgt;gt; Composelt;TSource, TIntermediate, TResultgt;( this Expressionlt;Funclt;TSource, TIntermediategt;gt; first, Expressionlt;Funclt;TIntermediate, TResultgt;gt; second) { var param = Expression.Parameter(typeof(TSource)); var intermediateValue = first.Body.ReplaceParameter(first.Parameters[0], param); var body = second.Body.ReplaceParameter(second.Parameters[0], intermediateValue); return Expression.Lambdalt;Funclt;TSource, TResultgt;gt;(body, param); }
Он использует следующий метод для замены параметра выражения выражением.
public static Expression ReplaceParameter(this Expression expression, ParameterExpression toReplace, Expression newExpression) { return new ParameterReplaceVisitor(toReplace, newExpression) .Visit(expression); } public class ParameterReplaceVisitor : ExpressionVisitor { private ParameterExpression from; private Expression to; public ParameterReplaceVisitor(ParameterExpression from, Expression to) { this.from = from; this.to = to; } protected override Expression VisitParameter(ParameterExpression node) { return node == from ? to : node; } }
Это позволяет вам писать свой код следующим образом:
public IQueryablelt;Tgt; Applylt;Tgt;(IQueryablelt;Tgt; source, Expressionlt;Funclt;T, DateTimegt;gt; dateField) { var result = source; if (BeginDate.HasValue) { if (BeginInclusive) result = result.Where(dateField.Compose(date =gt; date gt;= BeginDate)); else result = result.Where(dateField.Compose(date =gt; date gt; BeginDate)); } if (EndDate.HasValue) { if (EndInclusive) result = result.Where(dateField.Compose(date =gt; date lt;= EndDate)); else result = result.Where(dateField.Compose(date =gt; date lt; EndDate)); } return result; }
Комментарии:
1. Спасибо. Вы сэкономили мне час или даже больше на том, чтобы самому реализовать это. Жаль, что BCL не предоставляет этот метод
Ответ №2:
Вам придется создавать вручную dateField gt;= BeginDate
, используя методы класса выражений.
(...) if (BeginInclusive) { var greaterOrEqual = Expression.Lambdalt;Funclt;T, boolgt;gt;( Expression.GreaterThanOrEqual( dateField.Body, Expression.Constant(BeginDate)), dateField.Parameters); result = result.Where(greaterOrEqual); } (...)
Аналогично и в других случаях.
Комментарии:
1. Потрясающе, это именно то, что я придумал. Я собираюсь опубликовать еще один ответ с окончательным кодом, чтобы люди могли просто использовать его. Мне просто нужно подтвердить, что все это работает с Linq-to-Сущностями, как и ожидалось.
2. Я пытаюсь использовать
IEnumerablelt;Tgt;
его для собственного обучения, но я не могу его скомпилировать, вы видите, что я пропускаю ?public IEnumerablelt;Tgt; Applylt;Tgt;(IEnumerablelt;Tgt; source, Funclt;IEnumerablelt;Tgt;, DateTime, boolgt; dateField) { var result = source; return result.Where(x=gt;dateField(source,x)); }
Ответ №3:
Вот обновленный метод Apply, созданный после выяснения этого.
public IQueryablelt;Tgt; Applylt;Tgt;( IQueryablelt;Tgt; source, Expressionlt;Funclt;T,DateTimegt;gt; dateField ) { Expression predicate; if (BeginDate.HasValue) { if (BeginInclusive) predicate = Expression.GreaterThanOrEqual( dateField.Body, Expression.Constant( BeginDate, typeof(DateTime) ) ); else predicate = Expression.GreaterThan( dateField.Body, Expression.Constant( BeginDate, typeof(DateTime) ) ); source = source.Where( Expression.Lambdalt;Funclt;T, boolgt;gt;( predicate ) ); } if (EndDate.HasValue) { if (EndInclusive) predicate = Expression.LessThanOrEqual( dateField.Body, Expression.Constant( EndDate, typeof(DateTime) ) ); else predicate = Expression.LessThan( dateField.Body, Expression.Constant( EndDate, typeof(DateTime) ) ); source = source.Where( Expression.Lambdalt;Funclt;T, boolgt;gt;( predicate ) ); } return source; }
Затем я преобразую его в метод расширения, чтобы его можно было использовать следующим образом:
DateRange range; IQueryablelt;Tgt; q; q = q.WhereInDateRange( range, x =gt; x.DateField );