You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 23:35:34