As you already noticed, custom functions cannot be used as part of the query expression tree. So you either have to embed the function logic manually inside the query, thus introducing a lot of code duplication, or switch to method syntax and use helper methods that return the whole expression.
The later can be done manually using the methods of System.Linq.Expressions, but that's not natural and requires a lot of knowledge. Let me present you a \n easier way.
The goal would be to implement extension method like this
public static IQueryable<T> WhereIsCloseTo<T>(this IQueryable<T> source, Expression<Func<T, double>> comparand, double comparison, double tolerance = EightDecimalPlaces)
{
return source.Where(...);
}
and use it as follows
var targets = db.Targets
.WhereIsCloseTo(item => item.RightAscension, ra.Value, WithinOneMinute)
.WhereIsCloseTo(item => item.Declination, dec.Value, WithinOneMinute);
Note that with this approach you cannot use &&, but chained Where produce the equivalent result.
First, let provide the expression equivalent of your original function
public static Expression<Func<double, bool>> IsCloseTo(double comparison, double tolerance = EightDecimalPlaces)
{
return comparand => Math.Abs(comparand - comparison) >= tolerance;
}
The problem is that it can't be used directly in our method because it needs Expression<Func<T, bool>>.
Luckily that can easily be done by using a little helper utility form my answer to 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);
}
}
}
Now we have everything needed, so our method is implemented simple like this:
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));
}