使用 Coordinate 和存储库模式在 x 英里内获得结果

Get results within x miles Using GeoCoordinate with respository pattern

我想获取用户输入位置 x 英里范围内的所有组织的列表。这将转换为 long/lat 位置。

组织以经度和纬度存储在数据库中。

我正在使用带 entity framework 的 MVC 和带存储库模式的工作单元来访问数据集。

这是我的 EntityRepository:

        public IQueryable<T> All
    {
        get
        {
            return dbSet;
        }
    }

    public IQueryable<T> AllIncluding(params System.Linq.Expressions.Expression<Func<T, object>>[] includeProperties)
    {
        IQueryable<T> query = dbSet;
        foreach (var includeProperty in includeProperties)
        {
            query = query.Include(includeProperty);
        }

        return query;
    }

    public IEnumerable<T> Where(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
    {
        return dbSet.Where(predicate).AsEnumerable();
    }

为了查询我的数据上下文中的数据,我为每个实体使用一个服务 class,一个 UOW 被注入到每个服务中。组织的服务电话是:

 public class OrgService :IOrgService
{
    private IUnitOfWork _UoW;

    public OrgService(IUnitOfWork UoW)
    {
        _UoW = UoW;
    }

    public Organisation GetOrgByID(int OrgID)
    {

        return _UoW.OrganisationRepo.Find(OrgID);
    }

    public IList<Organisation> GetAllOrgs()
    {
        return _UoW.OrganisationRepo.All.ToList();  
    }

    public IList<Organisation> GetOrgsByLocation(double lat, double lng, int range)
    {

      /// I need to return a list of all organisations within X miles

    }

 }

所有其他查询都按应有的方式工作,但是我没有尝试编写方法 GetOrgsByLocation()。这是我认为我需要获得结果的查询:

 var userLocation = new GeoCoordinate(lat, lng);
 var result = _UoW.OrganisationRepo.Where(x => new GeoCoordinate(x.Latitude, x.Longitude))
                              .Where(x => x.GetDistanceTo(userLocation) < radius).ToList();

当我尝试 运行 这个查询时,我得到:

"cannot implicitly convert type system.device.location.geoCoordinate to bool"

有人能帮忙吗?

** 更新 - 工作解决方案 **

 var userLocation = new GeoCoordinate(lat, lng);
        var nearbyOrganizations = _UoW.OrganisationRepo.All.ToList()
         .Select(x => new
         { //use an anonymous type or any type you want
             Org = x,
             Distance = new GeoCoordinate(x.Latitude, x.Longitude).GetDistanceTo(userLocation)
         }) 
         .Where(x => x.Distance < 50000)
         .ToList();

        foreach (var organisation in nearbyOrganizations)
        {
            Console.WriteLine("{0} ({1:n0} meters)", organisation.Org, organisation.Distance);
        }

感谢下面这个解决方案的帮助,虽然似乎所有对象都必须在 orfer 中查询才能工作,但似乎查询更适合数据库上的 运行,我'我必须再仔细研究一下。

Where 方法必须 return 一个布尔值。

_UoW.OrganisationRepo.Where(x => new GeoCoordinate(x.Latitude, x.Longitude))

也许你打算在那里使用 .Select?以下代码是否有效?

_UoW.OrganisationRepo.Select(x => new GeoCoordinate(x.Latitude, x.Longitude))
                          .Where(x => x.GetDistanceTo(userLocation) < radius).ToList();

请注意 Entity Framework 试图从提供的表达式中生成一些 SQL。恐怕 x.GetDistanceTo(userLocation) 可能无法在 .Where 表达式中工作,除非您将其转换为 IEnumerable,或调用 .AsEnumerable().ToList().ToArray() 在调用 .Where 之前。或者也许 EF 足够聪明,可以看到 GeoCoordinate 没有映射到 table,然后就停止生成 SQL。

编辑

您评论的代码将无法运行:

_UoW.OrganisationRepo.All.Select(x => new GeoCoordinate(x.Latitude, x.Longitude).GetDistanceTo(userLocation) < radius).ToList()

请注意,您选择的是布尔值列表,因为您选择的是结果而不是按结果过滤。您不会知道半径内有哪些组织。这就是我们分别使用 .Select 和 .Where 的原因。

尝试这样的事情:

_UoW.OrganisationRepo.All
     .Select(x => new GeoCoordinate(x.Latitude, x.Longitude))
     .ToEnumerable() //or .ToList() or .ToArray(), make sure it's outside of EF's reach (prevent SQL generation for this)
     .Where(x=> x.GetDistanceTo(userLocation) < radius).ToList()

但是,如果您想知道半径内有哪些组织,则需要沿路径携带更多信息。

var nearbyOrganizations = _UoW.OrganisationRepo.All.ToList()
     .Select(x => new 
      { //use an anonymous type or any type you want
          Org = x, 
          Distance = new GeoCoordinate(x.Latitude, x.Longitude).GetDistanceTo(userLocation) 
     }) //it's probably outside EF's SQL generation step by now, but you could add one more .Select here that does the math if it fails (return the GeoCoordinate object)
     .Where(x=> x.Distance < radius)
     .ToList();

似乎了解更多关于 .Where 和 .Select 的信息对您很有用。它们非常有用。 .Where 本质上是一个过滤器,.Select 转换对象。由于匿名类型,你可以做任何你想做的事。

请注意,这将从数据库中获取所有对象。您可能希望使用数据库的本机功能来处理地理数据,而 EF 可能支持它。

我认为你的问题出在你的第一个 Where 上,因为你正在尝试创建一个新的 GeoCoordinate class 并且 Where 期望来自 lambda 的 bool 输出而不是 GeoCoordinate 实例.

我建议进行以下更改:

var result = _UoW.OrganisationRepo.Where(x => new GeoCoordinate(x.Latitude, x.Longitude).GetDistanceTo(userLocation) < radius).ToList();

这将为您提供您感兴趣的半径范围内的组织列表。

更新

先验无法与 IQueryable 一起使用,因为提供者将无法创建 SQL 查询,因为 .GetDistanceTo 函数在 SQL.

中未知

作为替代方案,您能否不使用 EF 5 及更高版本中的空间数据类型?

Rick Strahl 的这篇博客 post 给出了一个示例,该示例根据与给定位置

的距离查询存储在 SQL 中的地理数据

https://weblog.west-wind.com/posts/2012/jun/21/basic-spatial-data-with-sql-server-and-entity-framework-50