查找SQLite数据库中与给定坐标最近地点的最快实现方案
解决方案
核心问题原因
你的报错是因为EF Core无法将C#的Math.Pow方法翻译为SQLite可识别的原生函数,你强制用AsEnumerable会将全表数据加载到内存计算,数据量一大必然卡顿。另外你代码中的GroupBy完全是多余操作,仅需按距离排序即可。
最优方案1:小范围场景最快实现(欧氏距离)
直接将平方运算替换为乘法,不需要调用Math.Pow,整个查询可完全在数据库端执行,仅返回需要的前N条数据,性能最优:
public List<Place> GetNearestPlace(double latitude, double longitude, int take) { return _dbContext.Places .Where(x => x.Latitude.HasValue && x.Longitude.HasValue) .OrderBy(x => // 直接用乘法计算平方,EF Core可直接翻译为SQL运算 (latitude - x.Latitude.Value) * (latitude - x.Latitude.Value) + (longitude - x.Longitude.Value) * (longitude - x.Longitude.Value) ) .Take(take) .ToList(); }
性能提升建议
给Places表的Latitude和Longitude字段建立联合索引,可进一步避免全表扫描,查询速度提升数倍。
最优方案2:跨区域高精度实现(球面距离)
如果你需要计算地球球面的真实距离(避免跨省市时欧氏距离的误差),可使用Haversine公式,EF Core可翻译这类数学函数到SQLite执行,性能仍然远高于客户端计算:
public List<Place> GetNearestPlaceExact(double latitude, double longitude, int take) { const double radCoefficient = Math.PI / 180; double inputLatRad = latitude * radCoefficient; double inputLonRad = longitude * radCoefficient; // 地球半径,单位为千米,需要英里可替换为3956 const double earthRadius = 6371; return _dbContext.Places .Where(x => x.Latitude.HasValue && x.Longitude.HasValue) .OrderBy(x => 2 * earthRadius * Math.Asin( Math.Sqrt( Math.Sin((x.Latitude.Value * radCoefficient - inputLatRad) / 2) * Math.Sin((x.Latitude.Value * radCoefficient - inputLatRad) / 2) + Math.Cos(inputLatRad) * Math.Cos(x.Latitude.Value * radCoefficient) * Math.Sin((x.Longitude.Value * radCoefficient - inputLonRad) / 2) * Math.Sin((x.Longitude.Value * radCoefficient - inputLonRad) / 2) ) ) ) .Take(take) .ToList(); }
内容的提问来源于stack exchange,提问作者Danial Ahmed
相关产品推荐
相关产品推荐

