Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/sql-server/25.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 可以将SqlGeography与LINQtoSQL一起使用吗?_C#_Sql Server_Linq To Sql_Geography_Sqlgeography - Fatal编程技术网

C# 可以将SqlGeography与LINQtoSQL一起使用吗?

C# 可以将SqlGeography与LINQtoSQL一起使用吗?,c#,sql-server,linq-to-sql,geography,sqlgeography,C#,Sql Server,Linq To Sql,Geography,Sqlgeography,我在尝试使用Microsoft.SqlServer.Types.SqlGeography时遇到了不少问题。我很清楚,在LINQtoSQL中对此的支持不是很好。我尝试了许多方法,首先是预期的方法(数据库类型为geography,CLR类型为SqlGeography)。这就产生了NotSupportedException,这在博客上被广泛讨论 然后我将geography列视为varbinary(max),因为geography是存储为二进制的UDT。这似乎很好(使用一些二进制读写扩展方法) 然而,我

我在尝试使用
Microsoft.SqlServer.Types.SqlGeography
时遇到了不少问题。我很清楚,在LINQtoSQL中对此的支持不是很好。我尝试了许多方法,首先是预期的方法(数据库类型为
geography
,CLR类型为
SqlGeography
)。这就产生了
NotSupportedException
,这在博客上被广泛讨论

然后我将
geography
列视为
varbinary(max)
,因为
geography
是存储为二进制的UDT。这似乎很好(使用一些二进制读写扩展方法)

然而,我现在遇到了一个相当模糊的问题,这似乎没有发生在其他许多人身上

System.InvalidCastException:无法将“Microsoft.SqlServer.Types.SqlGeography”类型的对象强制转换为“System.Byte[]”类型

迭代查询时,会从
ObjectMaterialer
引发此错误。只有当包含地理列的表隐式地包含在查询中时(即使用
EntityRef
属性进行连接),才会出现这种情况

System.Data.Linq.SqlClient.ObjectReaderCompiler.ObjectReader`2.MoveNext()

我的问题:如果我以
varbinary(max)
的形式检索
geography
列,可能会出现相反的错误:无法将
byte[]
强制转换为
SqlGeography
。我会理解的。这个我不知道。我有一些关于部分LINQ到SQL类的属性,它们隐藏了二进制转换。。。这就是问题所在吗

感谢您的帮助,我知道可能没有足够的信息

额外费用:

  • Visual Studio dbml设计器中具有“服务器数据类型”的
    geography
    列生成此错误:
    指定的类型“geography”不是有效的提供程序类型。
  • Visual Studio dbml设计器中没有“服务器数据类型”的
    geography
    列生成此错误:
    无法将节点“值”格式化为SQL执行。

Linq to SQL不支持空间类型。支持不是“不好”——它不存在

您可以将它们读取为blob,但不能通过简单地将Linq中的列类型更改为SQL来实现。您需要使用
CAST
语句在数据库级别更改查询,以
varbinary
的形式返回列。您可以在表级别通过添加computed
varbinary
列来实现这一点,Linq很乐意将该列映射到
byte[]

换句话说,有些DDL是这样的:

ALTER TABLE FooTable
ADD LocationData AS CAST(Location AS varbinary(max))
然后,从Linq to SQL类中删除
Location
列,并改用
LocationData

如果随后需要访问实际的
SqlGeography
实例,则需要使用and将其转换为字节数组和字节数组

通过扩展部分Linq to SQL实体类并添加自动转换属性,可以使此过程更“自动化”:

public partial class Foo
{
    public SqlGeography Location
    {
        get { return SqlGeography.STGeomFromWKB(LocationData, 4326); }
        set { LocationData = value.STAsBinary(); }
    }
}
这假设
LocationData
是计算的
varbinary
列的名称;在Linq to SQL定义中不包括“real”
Location
列,而是以上面的即席方式添加它


还要注意的是,除了读和写之外,你将无法对这个专栏做更多的事情;如果您尝试对其进行实际查询(即将其包含在
Where
谓词中),那么您将得到一个类似的
NotSupportedException

如果您只想使用SqlGeography跟踪点并利用SQL Server 2008的空间索引,您可以,正如其他人所指出的,从Linq到SQL隐藏空间数据列,并使用UDF或存储过程。假设您有一个包含纬度和经度字段的表AddressFields。将该表添加到DBML文件中,并编写设置纬度和经度字段所需的任何代码。然后,下面的SQL代码将向该表添加一个Geo geogarphy字段,并在数据库中创建一个触发器,该触发器根据纬度和经度字段自动设置Geo字段。同时,下面的代码还创建了其他有用的UDF和存储过程:DistanceBetween2(我已经有了DistanceBetween)返回AddressField中表示的地址与指定纬度/经度对之间的距离;DistanceInthein返回指定英里距离内所有AddressFields的各种字段;UDFDistanceWithin的作用与用户定义的函数相同(如果希望将其嵌入到更大的查询中,则很有用);UDFnearestNeights从AddressField返回与指定数量的最近于特定点的邻居对应的字段。(使用UDFnearestNeights的一个原因是,如果只通过调用DistanceBetween2调用order,SQL Server 2008将不会优化其对空间索引的使用。)

您需要通过将AddressFields更改为您的表并自定义该表中要返回的字段(查看有关AddressFieldID引用的代码)来自定义此字段。然后,您可以在数据库上运行它,并将生成的存储过程和UDF复制到DBML上,然后在查询中使用它们。总的来说,这允许您相当容易地利用点的空间索引

-----------------------------------------------------------------------------------------
--[1]
--[2]
--[3] 创建索引
--[4] 创建程序USP\U SET\U GEO\U值第1段纬度2经度
--[5] 在横向/纵向值更改/插入时创建触发器--->设置地理代码
--[6] 创建过程USP\U SET\U GEO\U VALUE\U初始\U LOAD-->仅一次运行
--[7] 现有PROC distance between,返回指定两点之间的距离

--通过纬度/经度坐标对--更改进程之间的距离2 去 --试验

12159,40.75889600,-73.99228900之间的距离


--[8] 铬
--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
--SET GEO VALUE
GO
UPDATE AddressFields
SET Geo = geography::STPointFromText('POINT(' + CAST([Longitude] AS VARCHAR(20)) + ' ' + 
                    CAST([Latitude] AS VARCHAR(20)) + ')', 4326)
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
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
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
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
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
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
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
GO
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