【问题标题】:Getting Entity Framework to work with SQL Server geospatial indexes让实体框架与 SQL Server 地理空间索引一起工作
【发布时间】:2015-09-04 15:20:06
【问题描述】:

我们有一个相当简单的表,我们正在尝试使用 Entity Framework 查询它,它看起来像这样:

CREATE TABLE [dbo].[EFBlkFireRisks](
    [EFBlkFireRiskId] [int] IDENTITY(1,1) NOT NULL,
    [EFStateId] [int] NOT NULL,
    [Lat] [decimal](18, 8) NOT NULL,
    [Lon] [decimal](18, 8) NOT NULL,
    [AdjustedPremiumMultiplier] [decimal](18, 4) NOT NULL DEFAULT ((0)),
    [GeoLocation] [geography] NULL,
 CONSTRAINT [PK_dbo.EFBlkFireRisks] PRIMARY KEY CLUSTERED 
(
    [EFBlkFireRiskId] ASC
)

它在 GeoLocation 列上有一个地理空间索引,如下所示:

CREATE SPATIAL INDEX [IX_EFBlkFireRisks_Spatial] ON [dbo].[EFBlkFireRisks]
(
    [GeoLocation]
)USING  GEOGRAPHY_AUTO_GRID

我们正在尝试在该表中找到最接近给定纬度/经度点的条目。

C# 查询如下所示:

var geoPoint = SqlSchemaFunctions.GetDbGeography(lat, lon);
var query = ctx.BulkFireRisks
    .Where(x => x.EFStateId == stateId && x.GeoLocation.Distance(geoPoint) != null)
    .OrderBy(x => x.GeoLocation.Distance(geoPoint));
var bfr = query.First();

查询生成的 SQL 基本上是这样的:

declare @p3 sys.geography set @p3=convert(sys.geography,0xE6100000010C8FC2F5285CFF434052B81E85EB515AC0)
declare @p4 sys.geography set @p4=convert(sys.geography,0xE6100000010C8FC2F5285CFF434052B81E85EB515AC0)
exec sp_executesql N'SELECT TOP (1) 
    [Project1].[EFBlkFireRiskId] AS [EFBlkFireRiskId], 
    [Project1].[EFStateId] AS [EFStateId], 
    [Project1].[GeoId] AS [GeoId], 
    [Project1].[Lat] AS [Lat], 
    [Project1].[Lon] AS [Lon], 
    [Project1].[GeoLocation] AS [GeoLocation], 
    [Project1].[AdjustedPremiumMultiplier] AS [AdjustedPremiumMultiplier],
    [Project1].C1 as Distance
    FROM ( SELECT 
        [Extent1].[GeoLocation].STDistance(@p__linq__1) AS [C1], 
        [Extent1].[EFBlkFireRiskId] AS [EFBlkFireRiskId], 
        [Extent1].[EFStateId] AS [EFStateId], 
        [Extent1].[GeoId] AS [GeoId], 
        [Extent1].[Lat] AS [Lat], 
        [Extent1].[Lon] AS [Lon], 
        [Extent1].[GeoLocation] AS [GeoLocation], 
        [Extent1].[AdjustedPremiumMultiplier] AS [AdjustedPremiumMultiplier]
        FROM [dbo].[EFBlkFireRisks] AS [Extent1]
        WHERE (11 = [Extent1].[EFStateId]) AND ([Extent1].[GeoLocation].STDistance(@p__linq__0) IS NOT NULL)
    )  AS [Project1]
    ORDER BY [Project1].[C1] ASC',N'@p__linq__0 [geography],@p__linq__1 [geography]',@p__linq__0=@p3,@p__linq__1=@p4

并且该查询使用索引 - 将其置于循环中并运行 10 次大约需要 30 秒(无论我是通过 SQL 手动执行它,还是通过 C# 中的 EF 运行它) .

但是,可以删除sp_executesql 位,然后直接运行查询,如下所示:

declare @p3 sys.geography set @p3=convert(sys.geography,0xE6100000010C8FC2F5285CFF434052B81E85EB515AC0)
SELECT TOP (1) 
    [Project1].[EFBlkFireRiskId] AS [EFBlkFireRiskId], 
    [Project1].[EFStateId] AS [EFStateId], 
    [Project1].[GeoId] AS [GeoId], 
    [Project1].[Lat] AS [Lat], 
    [Project1].[Lon] AS [Lon], 
    [Project1].[GeoLocation] AS [GeoLocation], 
    [Project1].[AdjustedPremiumMultiplier] AS [AdjustedPremiumMultiplier],
    [Project1].C1 as Distance
    FROM ( SELECT 
        [Extent1].[GeoLocation].STDistance(@p3) AS [C1], 
        [Extent1].[EFBlkFireRiskId] AS [EFBlkFireRiskId], 
        [Extent1].[EFStateId] AS [EFStateId], 
        [Extent1].[GeoId] AS [GeoId], 
        [Extent1].[Lat] AS [Lat], 
        [Extent1].[Lon] AS [Lon], 
        [Extent1].[GeoLocation] AS [GeoLocation], 
        [Extent1].[AdjustedPremiumMultiplier] AS [AdjustedPremiumMultiplier]
        FROM [dbo].[EFBlkFireRisks] AS [Extent1]
        WHERE (11 = [Extent1].[EFStateId]) AND ([Extent1].[GeoLocation].STDistance(@p3) IS NOT NULL)
    )  AS [Project1]
    ORDER BY [Project1].[C1] asc

那个查询运行的时间不是 30 ,而是 30 毫秒

我在挠头。知道为什么这两个查询具有如此完全不同的执行模式吗?知道如何让 EF 使用这个特定的地理空间索引吗?我做错了什么?

编辑 6/18 - 如果我尝试向较慢的 sp_executesql 查询添加索引提示,我会收到以下错误消息:

查询处理器无法为带有空间索引提示的查询生成查询计划。原因:空间索引不支持谓词中提供的比较器。

同样的提示在另一个查询中有效 - 或者至少不会出错。

慢查询的查询计划如下:

换句话说,它没有获取地理空间索引。快速版本的查询计划正在获取索引,并且看起来像您期望的那样:

这是在 SQL Server 2014 上运行的:

(Microsoft SQL Server 2014 - 12.0.2000.8 (X64) 2014 年 2 月 20 日 20:04:26 版权所有 (c) 微软公司 Windows NT 6.3(内部版本 9600:)上的 Express 版(64 位)

【问题讨论】:

  • 嗯,那似乎是 MS SQL 服务器的问题。您是否尝试过使用提供的查询分析器工具? (见stackoverflow.com/questions/3983386/…
  • 另外,在您的查询中,如果您使用 with 关键字强制索引会发生什么?
  • @flindeberg - 快速查询的查询计划看起来像您期望的那样。慢查询的查询计划只显示为Execute Proc: Cost 0.0%。我已经编辑了上面的答案,以显示当我尝试使用 WITH (INDEX (IX_EFBlkFireRisks_Spatial)) 提示时会发生什么。
  • @flindeberg - 见我上面的编辑。 with 提示出错,慢查询的查询计划试图脱离主聚集索引 - 它没有使用(并且显然无法使用)地理空间索引。

标签: c# sql-server entity-framework geospatial


【解决方案1】:

好的,我想通了。

事实证明这两个查询并不完全相同:sp_executesql 版本有 两个 参数,而快速版本只有一个:这是关键。这个查询(只有一个参数)像你期望的那样执行:

declare @p3 sys.geography
set @p3=convert(sys.geography,0xE6100000010C8FC2F5285CFF434052B81E85EB515AC0)
exec sp_executesql N'SELECT TOP (1) 
    [Project1].[EFBlkFireRiskId] AS [EFBlkFireRiskId], 
    [Project1].[EFStateId] AS [EFStateId], 
    [Project1].[GeoId] AS [GeoId], 
    [Project1].[Lat] AS [Lat], 
    [Project1].[Lon] AS [Lon], 
    [Project1].[GeoLocation] AS [GeoLocation], 
    [Project1].[AdjustedPremiumMultiplier] AS [AdjustedPremiumMultiplier]
    FROM ( SELECT 
        [Extent1].[EFBlkFireRiskId] AS [EFBlkFireRiskId], 
        [Extent1].[EFStateId] AS [EFStateId], 
        [Extent1].[GeoId] AS [GeoId], 
        [Extent1].[Lat] AS [Lat], 
        [Extent1].[Lon] AS [Lon], 
        [Extent1].[GeoLocation] AS [GeoLocation], 
        [Extent1].[AdjustedPremiumMultiplier] AS [AdjustedPremiumMultiplier], 
        [Extent1].[GeoLocation].STDistance(@p__linq__0) AS [C1]
        FROM [dbo].[EFBlkFireRisks] AS [Extent1]
        WHERE (11 = [Extent1].[EFStateId]) AND ([Extent1].[GeoLocation].STDistance(@p__linq__0) IS NOT NULL)
    )  AS [Project1]
    ORDER BY [Project1].[C1] ASC',N'@p__linq__0 [geography]',@p__linq__0=@p3

在 LINQ 中获取该查询的方法是这样的:

var geoPoint = DbGeography.PointFromText(string.Format("POINT({0} {1})", lon, lat), 4326);
var q2 = (from b in ctx.BulkFireRisks
          let distance = b.GeoLocation.Distance(geoPoint)
          where b.EFStateId == stateId && distance != null
          orderby distance
          select b);
var bfr = q2.First();

关键是只需定义任何sys.geography 变量一次。

【讨论】:

  • 很高兴你明白了,这肯定会帮助更多:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多