cof*_*iem 10 c# sql-server geography linq-to-sql sqlgeography
我尝试使用时遇到了很多问题Microsoft.SqlServer.Types.SqlGeography
.我完全知道在Linq to Sql中对此的支持并不是很好.我尝试了很多方法,从预期的方式(数据库类型geography
,CLR类型SqlGeography
)开始.这产生了NotSupportedException
,这通过博客广泛讨论.
然后我走了将geography
列视为一个路径varbinary(max)
,就像geography
存储为二进制的UDT一样.这似乎工作正常(使用一些二进制读取和写入扩展方法).
但是,我现在遇到了一个相当模糊的问题,这似乎并没有发生在许多其他人身上.
System.InvalidCastException:无法将类型为"Microsoft.SqlServer.Types.SqlGeography"的对象强制转换为"System.Byte []".
ObjectMaterializer
在迭代查询时抛出此错误.它似乎只有在包含地理列的表隐式包含在查询中时(即使用EntityRef<>
属性进行连接)才会出现.
System.Data.Linq.SqlClient.ObjectReaderCompiler.ObjectReader`2.MoveNext()
我的问题:如果我正在检索geography
列varbinary(max)
,我可能会发现反向错误:无法转换byte[]
为SqlGeography
.我明白了.我没有.我对部分LINQ to SQL类有一些隐藏二进制转换的特性......那可能是问题吗?
任何帮助表示赞赏,我知道可能没有足够的信息.
附加功能:
geography
在Visual Studio DBML"服务器数据类型" =设计师列geography
生成此错误:The specified type 'geography' is not a valid provider type.
geography
在Visual Studio DBML没有"服务器数据类型"列设计师生成此错误:Could not format node 'Value' for execution as SQL.
mba*_*amo 17
如果你想用SqlGeography做的只是跟踪点并利用SQL Server 2008的空间索引,你可以像其他人所说的那样,将你的空间数据列从Linq隐藏到SQL并使用UDF或存储过程.假设您有一个包含纬度和经度字段的表AddressFields.将该表添加到您的DBML文件,并编写您想要设置纬度和经度字段的任何代码.然后,下面的SQL代码将向该表添加Geo geogarphy字段,并在数据库中创建一个触发器,该触发器根据纬度和经度字段自动设置Geo字段.同时,下面的代码还创建了其他有用的UDF和存储过程:DistanceBetween2(我已经有一个DistanceBetween)返回AddressField中表示的地址与指定的纬度/经度对之间的距离; DistanceWithin返回指定英里距离内所有AddressFields的各个字段; UDFDistanceWithin与用户定义的函数相同(如果要将其嵌入到更大的查询中,则非常有用); 和UDFNearestNeighbors从AddressField返回对应于最接近特定点的指定邻居数的字段.(使用UDFNearestNeighbors的一个原因是,如果您只是通过调用DistanceBetween2调用order,SQL Server 2008将不会优化其空间索引的使用.)UDFDistanceWithin与用户定义的函数相同(如果要将其嵌入到更大的查询中,则非常有用); 和UDFNearestNeighbors从AddressField返回对应于最接近特定点的指定邻居数的字段.(使用UDFNearestNeighbors的一个原因是,如果您只是通过调用DistanceBetween2调用order,SQL Server 2008将不会优化其空间索引的使用.)UDFDistanceWithin与用户定义的函数相同(如果要将其嵌入到更大的查询中,则非常有用); 和UDFNearestNeighbors从AddressField返回对应于最接近特定点的指定邻居数的字段.(使用UDFNearestNeighbors的一个原因是,如果您只是通过调用DistanceBetween2调用order,SQL Server 2008将不会优化其空间索引的使用.)
您需要通过将AddressFields更改为表并自定义要返回的表中的字段来自定义此项(查看对AddressFieldID的引用的代码).然后,您可以在数据库上运行它,并将生成的存储过程和UDF复制到DBML上,然后您可以在查询中使用它们.总的来说,这使您可以非常轻松地利用点的空间索引.
-----------------------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
--INITIAL AUDIT
select * from dbo.AddressFields
GO
--ADD COLUMN GEO
IF EXISTS (SELECT name FROM sysindexes WHERE name = 'SIndx_AddressFields_geo')
DROP INDEX SIndx_AddressFields_geo ON AddressFields
GO
IF EXISTS (SELECT b.name FROM sysobjects a, syscolumns b
WHERE a.id = b.id and a.name = 'AddressFields' and b.name ='Geo' and a.type ='U' )
ALTER TABLE AddressFields DROP COLUMN Geo
GO
alter table AddressFields add Geo geography
Run Code Online (Sandbox Code Playgroud)
--SET GEO VALUE
GO
UPDATE AddressFields
SET Geo = geography::STPointFromText('POINT(' + CAST([Longitude] AS VARCHAR(20)) + ' ' +
CAST([Latitude] AS VARCHAR(20)) + ')', 4326)
Run Code Online (Sandbox Code Playgroud)
IF EXISTS (SELECT name FROM sysindexes WHERE name = 'SIndx_AddressFields_geo')
DROP INDEX SIndx_AddressFields_geo ON AddressFields
GO
CREATE SPATIAL INDEX SIndx_AddressFields_geo
ON AddressFields(geo)
--UPDATE STATS
UPDATE STATISTICS AddressFields
--AUDIT
GO
select * from dbo.AddressFields
Run Code Online (Sandbox Code Playgroud)
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'USPSetGEOValue' AND type = 'P')
DROP PROC USPSetGEOValue
GO
GO
CREATE PROC USPSetGEOValue @latitude decimal(18,8), @longitude decimal(18,8)
AS
UPDATE AddressFields
SET Geo = geography::STPointFromText('POINT(' + CAST(@longitude AS VARCHAR(20)) + ' ' +
CAST(@latitude AS VARCHAR(20)) + ')', 4326)
WHERE [Longitude] =@longitude and [Latitude] = @latitude
GO
--TEST
EXEC USPSetGEOValue 38.87350500,-76.97627500
GO
Run Code Online (Sandbox Code Playgroud)
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'TRGSetGEOCode' AND type = 'TR')
DROP TRIGGER TRGSetGEOCode
GO
CREATE TRIGGER TRGSetGEOCode
ON AddressFields
AFTER INSERT,UPDATE
AS
DECLARE @latitude decimal(18,8), @longitude decimal(18,8)
IF ( UPDATE (Latitude) OR UPDATE (Longitude) )
BEGIN
SELECT @latitude = latitude ,@longitude = longitude from inserted
UPDATE AddressFields
SET Geo = geography::STPointFromText('POINT(' + CAST(@longitude AS VARCHAR(20)) + ' ' +
CAST(@latitude AS VARCHAR(20)) + ')', 4326)
WHERE [Longitude] =@longitude and [Latitude] = @latitude
END
ELSE
BEGIN
SELECT @latitude = latitude ,@longitude = longitude from inserted
UPDATE AddressFields
SET Geo = geography::STPointFromText('POINT(' + CAST(@longitude AS VARCHAR(20)) + ' ' +
CAST(@latitude AS VARCHAR(20)) + ')', 4326)
WHERE [Longitude] =@longitude and [Latitude] = @latitude
END
GO
Run Code Online (Sandbox Code Playgroud)
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'USPSetAllGeo' AND type = 'P')
DROP PROC USPSetAllGeo
GO
CREATE PROC USPSetAllGeo
AS
UPDATE AddressFields
SET Geo = geography::STPointFromText('POINT(' + CAST([Longitude] AS VARCHAR(20)) + ' ' +
CAST([Latitude] AS VARCHAR(20)) + ')', 4326)
GO
Run Code Online (Sandbox Code Playgroud)
- [7] EXISTING PROC DistanceBetween,返回指定的两点之间的距离
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'DistanceBetween2' AND type = 'FN')
DROP FUNCTION DistanceBetween2
GO
CREATE FUNCTION [dbo].[DistanceBetween2]
(@AddressFieldID as int, @Lat1 as real,@Long1 as real)
RETURNS real
AS
BEGIN
DECLARE @KMperNM float = 1.0/1.852;
DECLARE @nwi geography =(select geo from addressfields where AddressFieldID = @AddressFieldID)
DECLARE @edi geography = geography::STPointFromText('POINT(' + CAST(@Long1 AS VARCHAR(20)) + ' ' +
CAST(@Lat1 AS VARCHAR(20)) + ')', 4326)
DECLARE @dDistance as real = (SELECT (@nwi.STDistance(@edi)/1000.0) * @KMperNM)
return (@dDistance);
END
Run Code Online (Sandbox Code Playgroud)
GO --TEST
DistanceBetween2 12159,40.75889600,-73.99228900
- [8]创建程序USPDistanceWithin
IF EXISTS(SELECT name FROM sysobjects WHERE name ='USPDistanceWithin'AND type ='P')DROP PROCEDURE USPDistanceWithin
GO
CREATE PROCEDURE [dbo].USPDistanceWithin
(@lat as real,@long as real, @distance as float)
AS
BEGIN
DECLARE @edi geography = geography::STPointFromText('POINT(' + CAST(@Long AS VARCHAR(20)) + ' ' +
CAST(@Lat AS VARCHAR(20)) + ')', 4326)
SET @distance = @distance * 1609.344 -- convert distance into meter
select
AddressFieldID
,FieldID
,AddressString
,Latitude
,Longitude
,LastGeocode
,Status
--,Geo
from
AddressFields a WITH(INDEX(SIndx_AddressFields_geo))
where
a.geo.STDistance(@edi) < = @Distance
END
Run Code Online (Sandbox Code Playgroud)
走
- 测试
- 在3英里USPDistanceWithin 38.90606200,-76.92943500,3 GO - 在5英里USPDistanceWithin 38.90606200,-76.92943500,5 GO - 在10英里内USPDistanceWithin 38.90606200,-76.92943500,10
- [9]创建功能FNDistanceWithin
IF EXISTS(SELECT name FROM sysobjects WHERE name ='UDFDistanceWithin'AND type ='TF')DROP FUNCTION UDFDistanceWithin
GO
CREATE FUNCTION UDFDistanceWithin
(@lat as real,@long as real, @distance as real)
RETURNS @AddressIdsToReturn TABLE
(
AddressFieldID INT
,FieldID INT
)
AS
BEGIN
DECLARE @edi geography = geography::STPointFromText('POINT(' + CAST(@Long AS VARCHAR(20)) + ' ' +
CAST(@Lat AS VARCHAR(20)) + ')', 4326)
SET @distance = @distance * 1609.344 -- convert distance into meter
INSERT INTO @AddressIdsToReturn
select
AddressFieldID
,FieldID
from
AddressFields a WITH(INDEX(SIndx_AddressFields_geo))
where
a.geo.STDistance(@edi) < = @Distance
RETURN
END
Run Code Online (Sandbox Code Playgroud)
走
- 测试
- 在3英里内选择*来自UDFDistanceWithin(38.90606200,-76.92943500,3)GO - 在5英里内选择*来自UDFDistanceWithin(38.90606200,-76.92943500,5)GO - 在10英里处选择*来自UDFDistanceWithin(38.90606200,-76.92943500) ,10)
- [9] CREATE FUNCTION UDFNearestNeighbors
IF EXISTS(SELECT name FROM sysobjects WHERE name ='UDFNearestNeighbors'AND type ='TF')DROP FUNCTION UDFNearestNeighbors
GO
Run Code Online (Sandbox Code Playgroud)
IF EXISTS(SELECT name FROM sysobjects WHERE name ='numbers'AND xtype ='u')DROP TABLE数字
GO
-- First, create a Numbers table that we will use below.
SELECT TOP 100000 IDENTITY(int,1,1) AS n INTO numbers FROM MASTER..spt_values a, MASTER..spt_values b CREATE UNIQUE CLUSTERED INDEX idx_1 ON numbers(n)
GO
CREATE FUNCTION UDFNearestNeighbors
(@lat as real,@long as real, @neighbors as int)
RETURNS @AddressIdsToReturn TABLE
(
AddressFieldID INT
,FieldID INT
)
AS
BEGIN
DECLARE @edi geography = geography::STPointFromText('POINT(' + CAST(@Long AS VARCHAR(20)) + ' ' +
CAST(@Lat AS VARCHAR(20)) + ')', 4326)
DECLARE @start FLOAT = 1000;
WITH NearestPoints AS
(
SELECT TOP(@neighbors) WITH TIES *, AddressFields.geo.STDistance(@edi) AS dist
FROM Numbers JOIN AddressFields WITH(INDEX(SIndx_AddressFields_geo))
ON AddressFields.geo.STDistance(@edi) < @start*POWER(2,Numbers.n)
ORDER BY n
)
INSERT INTO @AddressIdsToReturn
SELECT TOP(@neighbors)
AddressFieldID
,FieldID
FROM NearestPoints
ORDER BY n DESC, dist
RETURN
END
Run Code Online (Sandbox Code Playgroud)
走
- 测试
--50邻居选择*来自UDFNearestNeighbors(38.90606200,-76.92943500,50)GO - 200邻居选择*来自UDFNearestNeighbors(38.90606200,-76.92943500,200)GO
Aar*_*ght 13
Linq to SQL不支持空间类型.支持不是"不太好" - 它不存在.
您可以将它们作为BLOB读取,但只需将Linq中的列类型更改为SQL即可完成此操作.您需要在数据库级别更改查询,以varbinary
使用该CAST
语句将列作为a返回.您可以通过添加计算varbinary
列在表级执行此操作,Linq将很乐意将其映射到a byte[]
.
换句话说,有些DDL是这样的:
ALTER TABLE FooTable
ADD LocationData AS CAST(Location AS varbinary(max))
Run Code Online (Sandbox Code Playgroud)
然后,Location
从Linq to SQL类中删除该列,然后使用LocationData
.
然后,如果您需要访问的实际SqlGeography
情况下,你需要将其转换为和从字节数组,使用STGeomFromWKB和STAsBinary.
您可以通过将部分Linq扩展为SQL实体类并添加自动转换属性来使此过程更加"自动":
public partial class Foo
{
public SqlGeography Location
{
get { return SqlGeography.STGeomFromWKB(LocationData, 4326); }
set { LocationData = value.STAsBinary(); }
}
}
Run Code Online (Sandbox Code Playgroud)
这假定它LocationData
是计算varbinary
列的名称; 您没有Location
在Linq to SQL定义中包含"真实" 列,您可以在上面的ad-hoc方式中添加它.
另请注意,除了读取和写入之外,您将无法对此列做很多事情; 如果你试图对它进行实际查询(即将它包含在Where
谓词中),那么你就会得到类似的东西NotSupportedException
.
归档时间: |
|
查看次数: |
14122 次 |
最近记录: |