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