在Xamarin.Forms中利用SQLite根据当前坐标筛选附近客户
基于Entity Framework Core + SQLite实现附近客户筛选
核心思路
由于SQLite没有原生地理空间函数,我们可以用Haversine公式计算两点间的大圆距离(适用于GPS坐标的距离计算),并通过EF Core将公式转换为SQL查询,筛选出当前位置100米范围内的客户。
修正基础代码
首先修正你代码中类名不一致的问题(Contact改为Customer):
public Customer GetCustomerById(long id) { return _db.Customers .Where(c => c.Id == id).FirstOrDefault(); }
方案1:直接在LINQ中实现Haversine公式
这种方式无需额外注册SQL函数,直接通过EF Core将C#的数学表达式转换为SQL语句:
using System; using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; public List<Customer> GetNearbyCustomers(decimal currentLatitude, decimal currentLongitude, double maxDistanceInMeters = 100) { // 转换当前坐标为弧度(Haversine公式要求弧度单位) var currentLatRad = (double)currentLatitude * Math.PI / 180; var currentLonRad = (double)currentLongitude * Math.PI / 180; const double EarthRadiusInMeters = 6371000; // 地球平均半径(米) var nearbyCustomers = _db.Customers // 过滤掉没有经纬度数据的客户 .Where(c => c.Latitude.HasValue && c.Longitude.HasValue) // 计算每个客户坐标的弧度值 .Select(c => new { Customer = c, CustomerLatRad = (double)c.Latitude * Math.PI / 180, CustomerLonRad = (double)c.Longitude * Math.PI / 180 }) // 应用Haversine公式计算距离 .Select(x => new { x.Customer, Distance = EarthRadiusInMeters * 2 * Math.Asin( Math.Sqrt( Math.Pow(Math.Sin((x.CustomerLatRad - currentLatRad) / 2), 2) + Math.Cos(currentLatRad) * Math.Cos(x.CustomerLatRad) * Math.Pow(Math.Sin((x.CustomerLonRad - currentLonRad) / 2), 2) ) ) }) // 筛选100米范围内的客户 .Where(x => x.Distance <= maxDistanceInMeters) // 按距离从近到远排序 .OrderBy(x => x.Distance) // 提取Customer实体 .Select(x => x.Customer) .ToList(); return nearbyCustomers; }
方案2:注册自定义SQL函数(更简洁)
如果觉得LINQ中的公式太冗长,可以在SQLite中注册自定义距离计算函数,再通过EF Core映射使用:
步骤1:创建SQLite自定义函数
在数据库初始化时执行以下SQL(可通过EF Core迁移或DbContext.Database.ExecuteSqlRaw执行):
CREATE FUNCTION CalculateDistance(lat1 REAL, lon1 REAL, lat2 REAL, lon2 REAL) RETURNS REAL AS BEGIN DECLARE earthRadius REAL = 6371000; DECLARE dLat REAL = (lat2 - lat1) * PI() / 180; DECLARE dLon REAL = (lon2 - lon1) * PI() / 180; DECLARE a REAL = SIN(dLat/2)*SIN(dLat/2) + COS(lat1*PI()/180)*COS(lat2*PI()/180)*SIN(dLon/2)*SIN(dLon/2); DECLARE c REAL = 2 * ASIN(SQRT(a)); RETURN earthRadius * c; END;
步骤2:在EF Core中注册函数
在你的DbContext类中添加以下代码:
using System; using Microsoft.EntityFrameworkCore; public class YourDbContext : DbContext { public DbSet<Customer> Customers { get; set; } // 注册自定义SQL函数的映射 protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.HasDbFunction(typeof(YourDbContext).GetMethod(nameof(CalculateDistance), new[] { typeof(double), typeof(double), typeof(double), typeof(double) })) .HasName("CalculateDistance"); } // 仅用于EF Core查询的占位方法(实际执行时会调用SQL函数) public static double CalculateDistance(double lat1, double lon1, double lat2, double lon2) { throw new NotImplementedException("此函数仅用于EF Core LINQ查询,不可直接调用"); } }
步骤3:简化查询代码
现在可以用更简洁的方式编写筛选逻辑:
public List<Customer> GetNearbyCustomers(decimal currentLatitude, decimal currentLongitude, double maxDistanceInMeters = 100) { return _db.Customers .Where(c => c.Latitude.HasValue && c.Longitude.HasValue) .Where(c => YourDbContext.CalculateDistance( (double)currentLatitude, (double)currentLongitude, (double)c.Latitude, (double)c.Longitude) <= maxDistanceInMeters) .OrderBy(c => YourDbContext.CalculateDistance( (double)currentLatitude, (double)currentLongitude, (double)c.Latitude, (double)c.Longitude)) .ToList(); }
注意事项
- 确保客户的
Latitude和Longitude字段不为空,否则会导致计算错误,因此查询中先过滤掉空值记录。 - Haversine公式计算的是近似距离(基于地球为球体的假设),对于短距离(如100米)误差可以忽略。
- EF Core会自动将C#中的
Math方法转换为SQLite对应的函数(如Math.PI→PI()、Math.Sin→SIN()等),无需额外处理。
内容的提问来源于stack exchange,提问作者Sid
相关产品推荐
相关产品推荐

