entity framework 实现按照距离排序

时间:2023-03-10 01:43:11
entity framework 实现按照距离排序

在做项目时,经常会遇到“离我最近”这种需求。顾名思义,它需要根据用户的经纬度和事物的经纬度计算距离,然后进行排序,最后分页(当然这些操作要在数据库中进行,否则就变成假分页了)。

我们通常可以用sql语句来实现

SELECT
es_name,
es_lon,
es_lat,
ROUND(
6378.138 * 2 * ASIN(
SQRT(
POW(
SIN(
(
30.611842 * PI() / 180 - es_lat * PI() / 180
) / 2
),
2
) + COS(30.611842 * PI() / 180) * COS(es_lat * PI() / 180) * POW(
SIN(
(
104.074666 * PI() / 180 - es_lon * PI() / 180
) / 2
),
2
)
)
) * 1000
) AS distance_um
FROM
c_ershuai
ORDER BY
distance_um ASC

但是我比较习惯使用 entity framework,于是我就想着能不能用 entity framework 实现按照距离排序。

以下是我采用的方案

首先定义一个接口,用来表示具有经纬度信息的实体。

    /// <summary>
/// 具有经纬度
/// </summary>
public interface IHasLngAndLat
{
/// <summary>
/// 经度
/// </summary>
double Lng { get; set; }
/// <summary>
/// 纬度
/// </summary>
double Lat { get; set; }
}

然后创建泛型类,用来包装计算的距离。

    /// <summary>
/// 带距离的数据
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public class DataWithDistance<TEntity>
{
/// <summary>
/// 距离(km)
/// </summary>
public double Distance { get; set; }
/// <summary>
/// 实体数据
/// </summary>
public TEntity Entity { get; set; }
}

最后编写根据距离排序的扩展方法

注意:这个方法是采用的 SqlFunctions 类,所以仅支持SqlServer数据库,如果是其它数据库,需要将 SqlFunctions 更换成对应的类

    /// <summary>
/// IQueryable扩展类
/// </summary>
public static class QueryableExtension
{
/// <summary>
/// 根据距离排序
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <param name="queryable"></param>
/// <param name="lng">经度</param>
/// <param name="lat">纬度</param>
/// <returns></returns>
public static IQueryable<DataWithDistance<TEntity>> OrderByDistance<TEntity>(this IQueryable<TEntity> queryable, double lng, double lat) where TEntity : class, IHasLngAndLat
{
var rtn = from q in queryable
let radLat1 = lat * Math.PI / 180.0
let radLat2 = q.Lat * Math.PI / 180.0
let a = radLat1 - radLat2
let b = lng * Math.PI / 180.0 - q.Lng * Math.PI / 180.0
let s = * SqlFunctions.Asin(SqlFunctions.SquareRoot(Math.Pow((double)SqlFunctions.Sin(a / ), ) +
SqlFunctions.Cos(radLat1) * SqlFunctions.Cos(radLat2) * Math.Pow((double)SqlFunctions.Sin(b / ), ))) * 6378.137
let d = Math.Round((double)s * ) /
orderby d
select new DataWithDistance<TEntity> { Entity = q, Distance = d }; return rtn;
}
}

以上就完成了 entity framework 按照距离排序的功能。

接下来我们用它来写一个小小的demo

首先创建一个商店实体类,具有经纬度字段,实现了  IHasLngAndLat 接口。

    /// <summary>
/// 商店实体
/// </summary>
public class Shop : IHasLngAndLat
{
/// <summary>
/// 主键
/// </summary>
public int Id { get; set; }
/// <summary>
/// 商店名称
/// </summary>
[Required]
[StringLength()]
public string ShopName { get; set; }
/// <summary>
/// 经度
/// </summary>
public double Lng { get; set; }
/// <summary>
/// 纬度
/// </summary>
public double Lat { get; set; }
}

然后创建EF上下文类

    /// <summary>
/// EF上下文
/// </summary>
public class DemoDbContext : DbContext
{
public DemoDbContext()
: base("name=DemoDbContext")
{
}
public virtual DbSet<Shop> Shop { get; set; }
}

最后我们分页查询商店,并按照距离由近到远排序

            #region 入参
double user_lng = 113.46, user_lat = 22.27; //用户经纬度
int pageIndex = ; //当前页码
int pageSize = ; //每页条数
#endregion using (DemoDbContext context = new DemoDbContext())
{
var queryable = context.Shop.AsNoTracking().AsQueryable();
IQueryable<DataWithDistance<Shop>> sort_queryable = queryable.OrderByDistance(user_lng, user_lat); //按照用户的距离从近到远排序
List<DataWithDistance<Shop>> data = sort_queryable.Skip((pageIndex - ) * pageSize).Take(pageSize).ToList(); //分页并执行sql查询获取数据 //TODO:将查到的数据映射成DTO对象,并返回给客户端
}

好了,entity framework 实现按照距离排序 也就全部完成了。