Как вы уже заметили, пользовательские функции не могут использоваться как часть дерева выражений запроса. Таким образом, вам нужно либо встроить логику функций вручную внутри запроса, тем самым внедрить много дублирования кода или переключиться на синтаксис метода и использовать вспомогательные методы, которые возвращают целое выражение.
Позже это можно сделать вручную, используя методы System.Linq.Expressions
, но это не является естественным и требует больших знаний. Позвольте мне представить вам более простой способ.
Цель будет реализовать метод расширения, как этот
public static IQueryable<T> WhereIsCloseTo<T>(this IQueryable<T> source, Expression<Func<T, double>> comparand, double comparison, double tolerance = EightDecimalPlaces)
{
return source.Where(...);
}
и использовать его следующим образом
var targets = db.Targets
.WhereIsCloseTo(item => item.RightAscension, ra.Value, WithinOneMinute)
.WhereIsCloseTo(item => item.Declination, dec.Value, WithinOneMinute);
Обратите внимание, что при таком подходе вы не можете использовать &&
, но прикован Where
производить эквивалентный результат ,
Во-первых, позвольте представить выражение эквивалент исходной функции
public static Expression<Func<double, bool>> IsCloseTo(double comparison, double tolerance = EightDecimalPlaces)
{
return comparand => Math.Abs(comparand - comparison) >= tolerance;
}
Проблема заключается в том, что он не может быть использован непосредственно в нашем методе, так как он нуждается в Expression<Func<T, bool>>
.
К счастью, это можно легко сделать, используя маленький помощник утилита сформировать свой ответ на Define part of an Expression as a variable in c#:
public static class ExpressionUtils
{
public static Expression<Func<TOuter, TResult>> Bind<TOuter, TInner, TResult>(this Expression<Func<TOuter, TInner>> source, Expression<Func<TInner, TResult>> resultSelector)
{
var body = new ParameterExpressionReplacer { source = resultSelector.Parameters[0], target = source.Body }.Visit(resultSelector.Body);
var lambda = Expression.Lambda<Func<TOuter, TResult>>(body, source.Parameters);
return lambda;
}
public static Expression<Func<TOuter, TResult>> ApplyTo<TInner, TResult, TOuter>(this Expression<Func<TInner, TResult>> source, Expression<Func<TOuter, TInner>> innerSelector)
{
return innerSelector.Bind(source);
}
class ParameterExpressionReplacer : ExpressionVisitor
{
public ParameterExpression source;
public Expression target;
protected override Expression VisitParameter(ParameterExpression node)
{
return node == source ? target : base.VisitParameter(node);
}
}
}
Теперь у нас есть все необходимое, так что наш метод реализован простой, как это:
public static IQueryable<T> WhereIsCloseTo<T>(this IQueryable<T> source, Expression<Func<T, double>> comparand, double comparison, double tolerance = EightDecimalPlaces)
{
return source.Where(IsCloseTo(comparison, tolerance).ApplyTo(comparand));
}
Принимая это как ответ - но я отправляю свой собственный ответ, чтобы точно показать, как я в конце концов это сделал. –
Ах, извините за то, что вы недостаточно думали о проблеме, чтобы понять, что вы имели в виду угловые «часы» и «минуты», а не время. Но принцип по-прежнему сохраняется, как вы продемонстрировали в своем ответе. – krillgar