entity framework 實現按照距離排序
阿新 • • 發佈:2018-10-31
在做專案時,經常會遇到“離我最近”這種需求。顧名思義,它需要根據使用者的經緯度和事物的經緯度計算距離,然後進行排序,最後分頁(當然這些操作要在資料庫中進行,否則就變成假分頁了)。
我們通常可以用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 實現按照距離排序 也就全部完成了。