.NET Core - IEnumerable 与 IQueryable 产生不同的空间/地理结果

Posted

技术标签:

【中文标题】.NET Core - IEnumerable 与 IQueryable 产生不同的空间/地理结果【英文标题】:.NET Core - Different spatial / geographic results produced in IEnumerable vs IQueryable 【发布时间】:2021-09-04 23:59:33 【问题描述】:

我们将 ASP.NET Core 5 后端与 SQL Server v15 和 Entity Framework V5 一起使用,我一直在努力解决一些有趣的问题,请考虑一下:

你有一个如下表:

CREATE TABLE [dbo].[ContactDetails](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](128) NOT NULL,
    ...
    [Geolocation] [geography] NULL
)

该表被其他表使用,例如。 Stores., 存储联系方式。

Table ContactDetails 会自动搭建如下:

public partial class ContactDetail

    public ContactDetail()
    
        Stores = new HashSet<Store>();
    

    public int Id  get; set; 
    public string Name  get; set; 
    public Geometry Geolocation  get; set; 
    

然后你从输入中存储商店的经度和纬度(双精度)让我们说:

Geometry Store.ContactDetails.Geolocation = new NetTopologySuite.Geometries.Point(store.longitude, store.latitude)  SRID = 4326 ;

现在给定一个来自用户的位置,它是以相同的方式创建的

Geometry userLocation = new NetTopologySuite.Geometries.Point(user.longitude, user.latitude)  SRID = 4326 ;

您想知道每个商店离用户有多远。你会怎么做?

我这样做了:

DbContext dbContext; //...somehow constructed
var dbSet = dbContext.Set<Store>();
var distances = dbSet.Where(...).Select(store => store.ContactDetails.Geolocation.Distance(userLocation));

但令我惊讶的是,距离完全消失了!我希望距离以度为单位返回,我将不得不将其转换为公里。但这些数字完全不成立(如 83130)。

所以我尝试了这个:

var anotherDistances = dbSet.Where(...).AsEnumerable().Select(store => store.ContactDetails.Geolocation.Distance(userLocation));

这次距离符合预期(例如 1.245 度)。

那里发生了什么? 我需要将结果保留为IQueryable,以便我可以有效地进行进一步的过滤和转换。如何在不将IQueryable 转换为IEnumerable 的情况下获得正确的距离?

我无法从其他问题中找到答案,我怀疑 Linq 无法正确地将查询转换为 SQL,并且在 AsEnumerable() 之后的查询能够使用正确的类型推断并在加载对象时使用空间正确的方法记忆。但这只是一些模糊的理解。如果您能帮助我理解这一点,我将不胜感激。

【问题讨论】:

【参考方案1】:

您可以使用IQuery&lt;&gt;.ToString() 来显示正在创建的实际SQL 查询吗?也许这会为您指明正确的方向。另外,也许this answer 很有趣。

此外,您可能想看看您正在使用的数据库。根据docs:

如果 EF Core 通过 SQL 对操作进行服务器评估,则结果的 单位由数据库决定。

【讨论】:

IQueryable.ToString() 只是打印出 IQueryable 对象的类型信息。但是我确实启用了数据库日志记录,它产生了这样的结果:``` SELECT [c].[Geolocation].STDistance(@__userLocation_0) AS [c] FROM [Stores] AS [s] INNER JOIN [ContactDetails] AS [c] ON [s].[ContactDetailsId] = [c].[Id] ``` 这对我来说看起来不错,但我不熟悉 SQL Server。但是,您在 SQL 语句中使用 [geography] 而在 C# 中使用 Geometry 是否正确?我认为两者的行为略有不同。 Dominik Berse,谢谢你的建议!你帮我找到正确的答案。翻译后的 SQL 使用不同的函数来测量距离,这些函数在语义上可能相似,但返回值不同。请看我上面的评论。再次感谢! 完美,很高兴能帮上忙!【参考方案2】:

啊,好吧,谜团解开了。感谢 Dominik 的 @dominik-berse 建议,我进一步挖掘。

当查询从 LINQ 转换为 SQL 时,被映射的函数不必是等价的。在这种情况下,NetTopologySuite.Geometries.Distance() 被转换为 geography::STDistance() - 根据 SRID 模型的类型返回不同单位的距离,如果是 4326 模型(由 GPS 使用),它是米。

当查询转换为对象时,直接调用对象的方法,在本例中为 NetTopologySuite.Geometries.Distance()。

而且这两个值可以完全不同。

【讨论】:

以上是关于.NET Core - IEnumerable 与 IQueryable 产生不同的空间/地理结果的主要内容,如果未能解决你的问题,请参考以下文章

asp.net core如何一一显示IEnumerable模型

在 NET.Core 中构建多/单条件搜索功能(IQueryable 或 IEnumerable)

由于 .net-core 中的 linq-statement 导致 IEnumerable 和 List 之间出现意外差异? [复制]

使用 IEnumerable<T> 在 .net core 中添加对象的实例

.Net Core 3.1 中的 IEnumerable.OrderBy().First() 优化是不是记录在任何地方?

在 ASP.NET Core 2.0 中将 DataTable 转换为 IEnumerable<T>