C# 获取实体框架以使用SQL Server地理空间索引
我们有一个非常简单的表,我们正试图用实体框架查询它,它看起来像这样:C# 获取实体框架以使用SQL Server地理空间索引,c#,sql-server,entity-framework,geospatial,C#,Sql Server,Entity Framework,Geospatial,我们有一个非常简单的表,我们正试图用实体框架查询它,它看起来像这样: 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, [AdjustedPremium
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
)
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();
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
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();
它在“地理位置”列上有一个地理空间索引,如下所示:
CREATE SPATIAL INDEX [IX_EFBlkFireRisks_Spatial] ON [dbo].[EFBlkFireRisks]
(
[GeoLocation]
)USING GEOGRAPHY_AUTO_GRID
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
我们试图在表中找到最接近给定纬度/经度点的条目
C#查询如下所示:
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
)
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();
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
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();
查询生成的SQL基本上如下所示:
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
)
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();
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
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();
该查询不使用索引——将其放入循环并运行10次大约需要30秒(无论是通过SQL手动执行,还是通过C#中的EF运行)
但是,可以删除sp_executesql
位,并直接运行查询,如下所示:
CREATE SPATIAL INDEX [IX_EFBlkFireRisks_Spatial] ON [dbo].[EFBlkFireRisks]
(
[GeoLocation]
)USING GEOGRAPHY_AUTO_GRID
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
查询添加索引提示,我会收到以下错误消息:
查询处理器无法为具有空间索引提示的查询生成查询计划。原因:空间索引不支持谓词中提供的比较器
同样的提示在另一个查询上有效——或者至少不会出错
慢速查询的查询计划如下所示:
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
)
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();
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
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();
换句话说,它没有获取地理空间索引。快速版本的查询计划正在提取索引,看起来像您期望的那样:
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
这在SQL Server 2014上运行:
(Microsoft SQL Server 2014-12.0.2000.8(X64)
2014年2月20日20:04:26
版权所有(c)微软公司
Windows NT 6.3(版本9600:)上的快速版(64位)
好的,我知道了
结果表明,这两个查询并不完全相同: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中获取该查询的方法如下:
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
)
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();
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
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
变量。Hms,似乎是MS SQL server的问题。您是否尝试过使用提供的查询分析器工具?(请参阅)另外,在您的查询中,如果通过使用with
关键字强制索引,会发生什么情况?@flindeberg-快速查询的查询计划与您预期的一样。慢速查询的查询计划仅显示为Execute Proc:Cost 0.0%
。我编辑了上面的答案,以显示尝试使用with(index)时会发生什么(IX_efblkfirefrisks_Spatial))
hint。@flindeberg-请参阅我上面的编辑。带有hint的错误被排除,慢速查询的查询计划试图脱离主聚集索引-它没有使用(显然无法使用)地理空间索引。很高兴你找到了它,这肯定会有更多帮助:)