.NET Core 2中使用DbGeography/SqlGeography的当前最优方案咨询
Hey there! Since you're stuck with EF Core 2.0.1 (which doesn't support spatial data) and need to build geolocation features for your .NET Core 2 WebApi, here are the most practical, up-to-date solutions tailored to your use case (storing lat/long + radius):
1. 直接使用数据库原生SQL处理空间计算
这是性能最优的方案,尤其是当你数据量较大的时候。大部分主流数据库(比如SQL Server、PostgreSQL)都原生支持空间数据类型和计算函数,哪怕EF Core 2.0.1没法映射这些类型,你依然可以:
- 在数据库里把经纬度存储为对应的空间类型(比如SQL Server的
GEOGRAPHY),或者保留单独的lat/long字段,用内置函数计算距离。 - 通过EF Core的
FromSql方法执行原生SQL查询,来实现半径范围内的地址筛选。
举个SQL Server的例子,假设你有Addresses表,包含Latitude、Longitude和Radius字段,要查询距离目标点(37.7749, -122.4194)在自身半径内的地址:
SELECT * FROM Addresses WHERE GEOGRAPHY::Point(Latitude, Longitude, 4326).STDistance(GEOGRAPHY::Point(37.7749, -122.4194, 4326)) <= Radius * 1000
然后在代码里调用:
var targetLat = 37.7749; var targetLong = -122.4194; var addresses = _context.Addresses .FromSql("SELECT * FROM Addresses WHERE GEOGRAPHY::Point(Latitude, Longitude, 4326).STDistance(GEOGRAPHY::Point({0}, {1}, 4326)) <= Radius * 1000", targetLat, targetLong) .ToList();
注意:一定要用参数化查询,避免SQL注入风险。
2. 应用层手动实现空间计算(适合小数据量场景)
如果你的数据量不大(比如几千条以内),可以先把所有地址的lat/long和radius从数据库取出来,然后在代码里用Haversine公式计算距离,再筛选符合条件的记录。
先写一个静态方法计算两点间的距离(单位:米):
public static double CalculateDistance(double lat1, double lon1, double lat2, double lon2) { var r = 6371000; // 地球半径,单位米 var dLat = ToRadians(lat2 - lat1); var dLon = ToRadians(lon2 - lon1); var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) + Math.Cos(ToRadians(lat1)) * Math.Cos(ToRadians(lat2)) * Math.Sin(dLon / 2) * Math.Sin(dLon / 2); var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a)); return r * c; } private static double ToRadians(double degrees) { return degrees * Math.PI / 180; }
然后在业务逻辑里使用:
var targetLat = 37.7749; var targetLong = -122.4194; var allAddresses = _context.Addresses.ToList(); var filteredAddresses = allAddresses .Where(a => CalculateDistance(a.Latitude, a.Longitude, targetLat, targetLong) <= a.Radius * 1000) .ToList();
提醒:这个方案的缺点是需要加载全表数据,数据量大时性能会下降,所以只适合小规模数据集。
3. 考虑升级EF Core版本(如果项目允许)
虽然你提到当前2.0.1仍不支持空间数据,但EF Core从2.2版本开始正式引入了空间数据支持。如果你的项目没有严格的版本限制,升级到EF Core 2.2或更高版本(比如3.1 LTS)会让你直接使用EF的空间数据API,比如DbSet<Address>里定义Point类型,用Linq查询a.Location.Distance(targetPoint) <= a.Radius,这会是最优雅的解决方案。不过如果升级涉及到项目依赖、兼容性问题,那前两个方案会更稳妥。
内容的提问来源于stack exchange,提问作者Matthew Flynn

