c# - 如何查询基于双列为 "close to"某个值的数据集?

标签 c# entity-framework linq sql-server-ce-4

我有一个引用数据库,其中包含天体网格上物体的坐标。我想查询数据库并找到“接近”(在特定角度距离内)给定点的对象。

我试过这个查询:

const double WithinOneMinute = 1.0 / 60.0;    // 1 minute of arc
var db = CompositionRoot.GetTargetDatabase();
var targets = from item in db.Targets
              where item.RightAscension.IsCloseTo(ra.Value, WithinOneMinute) 
                    && item.Declination.IsCloseTo(dec.Value, WithinOneMinute)
              select item;
var found = targets.ToList();

失败是因为 LINQ 查询提供程序不理解我的 IsCloseTo 扩展方法,该方法实现为:

public static bool IsCloseTo(this double comparand, double comparison, double tolerance = EightDecimalPlaces)
{
    var difference = Math.Abs(comparand - comparison);
    return (difference <= tolerance); 
}

所以我目前陷入了想法。有人做过这样的事情吗?

最佳答案

正如您已经注意到的,自定义函数不能用作查询表达式树的一部分。因此,您要么必须在查询中手动嵌入函数逻辑,从而引入大量代码重复,要么切换到方法语法并使用返回整个表达式的辅助方法。

后面可以使用System.Linq.Expressions的方法手动完成,但这并不自然,需要大量知识。让我向您介绍一种\n 更简单的方法。

目标是实现这样的扩展方法

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

关于c# - 如何查询基于双列为 "close to"某个值的数据集?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35430724/

相关文章:

.net - 为什么GroupBy多个字段无法正确分组>

c# - 使用 BasicHttpBinding 进行 WCF 自定义证书验证

c# - RazorEngine:Razor.Parse 每隔一段时间就会抛出 FileNotFoundException

c# - 使用 Mongo C# 驱动程序识别成功的插入

c# - EF 核心 : Foreign key returns null value

MySQL EF Core 所有字符串字段都创建为文本列

c# - 使用 LINQ,如何有条件地选择某些项目但没有条件选择全部?

c# - 使用 itextsharp 在 C# 中提取阿拉伯语文本

entity-framework - 为什么使用自定义 DbInitializer 的 EntityFramework 6.1 索引不起作用?

c# - 尝试使用 XPath 过滤 XML