Как я могу включить одно выражение в другое выражение?

#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 );