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 = 2 * SqlFunctions.Asin(SqlFunctions.SquareRoot(Math.Pow((double)SqlFunctions.Sin(a / 2), 2) +
               SqlFunctions.Cos(radLat1) * SqlFunctions.Cos(radLat2) * Math.Pow((double)SqlFunctions.Sin(b / 2), 2))) * 6378.137
                      let d = Math.Round((double)s * 10000) / 10000
                      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(64)]
        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 = 3; //当前页码
            int pageSize = 10; //每页条数
            #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 - 1) * pageSize).Take(pageSize).ToList();   //分页并执行sql查询获取数据

                //TODO:将查到的数据映射成DTO对象,并返回给客户端
            }

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

 

 

posted @ 2018-10-31 20:11  Woody~~~  阅读(924)  评论(1编辑  收藏  举报