包含SqlGeometry的数据表导致存储过程执行失败…为什么?

我正在尝试将一系列SqlGeometry值保存到SQL Server 2008数据库。

基本上我在SQL Server存储过程中有一个tabletype,如下所示:

 CREATE TYPE [dbo].[TableType_Example] AS TABLE ( [SpatialID] [bigint] NOT NULL, [RecordID] [bigint] NOT NULL, [geom] [geometry] NOT NULL ) 

然后我在C#中构建一个数据表并像这样发送:

 public static bool SaveSpatialDataElements(long recordID, List featureList) { //Setup features datatable DataTable dtFeatures = new DataTable(); dtFeatures.Columns.Add("SpatialID", typeof(SqlInt64)); dtFeatures.Columns.Add("RecordID", typeof(SqlInt64)); dtFeatures.Columns.Add("geom", typeof(SqlGeometry)); foreach(var curFeature in featureList) { object[] curRowObjects = new object[dtFeatures.Columns.Count]; curRowObjects[0] = curFeature.SpatialID; curRowObjects[1] = recordID; using (var reader = new StringReader(curFeature.ToGML())) { using (var xmlreader = new XmlTextReader(reader)) { curRowObjects[2] = SqlGeometry.GeomFromGml(new SqlXml(xmlreader), 0); } } DataRow row = dtFeatures.NewRow(); row.ItemArray = curRowObjects; dtFeatures.Rows.Add(row); } DbConn conn = new DbConn(); conn.Connect(); conn.ExecuteStoredProcedure(false, "USP_tblSpatialLocation_Update", recordID, dtFeatures); conn.Disconnect(); return true; } 

这适用于我的所有其他数据表,但这一个包含一个SqlGeometry列,它会出现错误消息:

System.Data.dll中出现“System.ArgumentException”类型的exception,但未在用户代码中处理

附加信息:不支持“geom”列的类型。 类型是’SqlGeometry’

这对我没有任何意义,因为我在文档中阅读的内容似乎支持该数据类型。

有什么想法吗?

编辑:

以下评论和我链接的文章: https : //viswaug.wordpress.com/2008/09/29/inserting-spatial-data-in-sql-server-2008/似乎表明我需要更改SqlGeometrySqlDbType.Udt 。 可悲的是,当我使用数据表时,我无法定义UdtTypeName = “GEOMETRY”; 因为这是在参数上设置的。

由于对您的问题做了简短的评论,我有机会充分利用这些选项。 看来,目前(甚至尝试.NET 4.6和SQL 2014)在定义DataTable的列时,不能将SqlGeographySqlGeometry设置为typeof()参数。 为了绝对清晰,您可以在.NET中执行此操作甚至填充它,但您无法将该表作为TVP传递到存储过程。

有两种选择。

选项1.传递WKT格式的值。

按如下方式定义表类型。

 CREATE TYPE [dbo].[WKT_Example] AS TABLE ( [geom] [varchar](max) NOT NULL ) 

然后按如下方式定义存储过程。

 CREATE PROCEDURE [dbo].[BulkInsertFromWKT] @rows [dbo].[WKT_Example] READONLY AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; INSERT INTO [dbo].[Table1] ([SpatialData]) SELECT geometry::STGeomFromText(R.[SpatialData], 4326) FROM @rows R; END 

按如下方式定义.NET DataTable:

 DataTable wktTable = new DataTable(); wktTable.Columns.Add("SpatialData", typeof(string)); 

填充如下:

 for (int j = 0; j < geometryCollection.Count; j++) { System.Data.SqlTypes.SqlString wkt = geometryCollection[j].STAsText().ToSqlString(); wktTable.Rows.Add(wkt.ToString()); } 

选项2.以WKB格式传递值。

按如下方式定义表类型。

 CREATE TYPE [dbo].[WKB_Example] AS TABLE ( [geom] [varbinary](max) NOT NULL ) 

然后按如下方式定义存储过程。

 CREATE PROCEDURE [dbo].[BulkInsertFromWKB] @rows [dbo].[WKB_Example] READONLY AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; INSERT INTO [dbo].[Table1] ([SpatialData]) SELECT geometry::STGeomFromWKB(R.[SpatialData], 4326) FROM @rows R; END 

按如下方式定义.NET DataTable:

 DataTable wkbTable = new DataTable(); wkbTable.Columns.Add("SpatialData", typeof(System.Data.SqlTypes.SqlBytes)); 

填充如下:

 for (int j = 0; j < geometryCollection.Count; j++) { wkbTable.Rows.Add(geographyCollection[j].STAsBinary()); } 

笔记:

按如下方式定义SqlParameter:

 SqlParameter p = new SqlParameter("@rows", SqlDbType.Structured); p.TypeName = "WKB_Example"; // The name of your table type p.Value = wkbTable; 

我从地理工作中留下了4326的SRID。 您可以将其更改为您想要的任何内容 - 事实上,如果您使用Geography我建议您将其作为第二个参数来为您提供灵活性。

此外,如果性能至关重要,您会发现更好地使用WKB。 我的测试发现WKB在WKT的45%到65%的时间内完成了。 这取决于数据的复杂程度和设置。

当您的存储过程具有[几何]或[地理]类型的参数时,您在将参数的UdtTypeName指定为“几何”/“地理”时找到的信息是正确的。 它不适用于TVP。

把它放在这里作为替代答案,以防万一其他人遇到这个并发现它有用,并且为了完整性尽可能多地包含这些信息。

如果您在c#代码中根本没有使用SqlGeometries(我只是将库包含在纯粹用于发送数据库值),那么坚持使用WKT似乎更有效。

最明确的方法就是Jon Bellamy在上面接受的答案中提出的建议。 然而,有一种更短的方式似乎表现更好。 基本上,数据库将隐式地将有效WKT值转换为存储过程中的几何。

例:

表类型

 CREATE TYPE [dbo].[WKT_Example] AS TABLE ( [geom] Geometry NOT NULL ) 

存储过程:

 CREATE PROCEDURE [dbo].[BulkInsertFromWKB] @rows [dbo].[WKB_Example] READONLY AS BEGIN INSERT INTO [dbo].[Table1] ([SpatialData]) SELECT geom FROM @rows END 

C#代码:

 DataTable wkbTable = new DataTable(); wkbTable.Columns.Add("SpatialData", typeof(SqlString)); for (int j = 0; j < arrOfWKT.Count; j++) { wkbTable.Rows.Add(arrOfWKT[j]); } 

只是为了澄清。 如果您的数据在c#代码中的格式为SqlGeometry,那么您可以更快地使用Jon Bellamy上面建议的WKB格式。

我还试图找出扩展数据表的方法,但没有管理。 但是,我确实找到了一个使用自定义迭代的好选择,它可能比数据表更快,并且更灵活。

结帐这些链接:
有关如何使用自定义迭代器将表值param(TVP)传递给SQL Server sproc的详细说明
在TVP中使用SQL几何的示例

值得注意的是,这种方法比WKT方法具有更轻的DB占用空间,因为它不需要DB将输入转换为可用格式。