1. 程式人生 > >entity framework 實現按照距離排序

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 實現按照距離排序 也就全部完成了。