包含 SqlGeometry 的数据表导致存储过程执行失败...为什么?
Datatable containing SqlGeometry is causing stored procedure execution to fail... Why?
我正在尝试将一系列 SqlGeometry
值保存到 SQL Server 2008 数据库。
基本上我在 SQL 服务器存储过程中有一个表类型,如下所示:
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<BOSpatial> 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
列,它因错误消息而失败:
An exception of type 'System.ArgumentException' occurred in
System.Data.dll but was not handled in user code
Additional information: The type of column 'geom' is not supported.
The type is 'SqlGeometry'
这对我来说没有任何意义,因为我在文档中阅读的内容似乎支持该数据类型。
有什么想法吗?
编辑:
下面的评论和我链接的文章:https://viswaug.wordpress.com/2008/09/29/inserting-spatial-data-in-sql-server-2008/ 似乎建议我需要将 SqlGeometry
的数据类型更改为 SqlDbType.Udt
。遗憾的是,当我使用数据表时,我无法定义 UdtTypeName = “GEOMETRY”;
,因为这是在参数上设置的。
自从对您的问题作了简短评论后,我就有机会充分了解这些选项。目前看来(甚至尝试使用 .NET 4.6 和 SQL 2014)在为 [=22] 定义列时无法将 SqlGeography
OR SqlGeometry
设置为 typeof()
参数=].为绝对清楚起见,您可以在 .NET 中执行此操作,甚至填充它,但您无法随后将 table 作为 TVP 传递给存储过程。
有两种选择。
选项 1. 以 WKT 格式传递值。
按如下方式定义您的 table 类型。
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 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 格式传递值。
按如下方式定义您的 table 类型。
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 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%。这将因数据的复杂性和设置而异。
当您的存储过程具有 [Geometry] 或 [Geography] 类型的参数时,您找到的有关将参数的 UdtTypeName
指定为 "Geometry" / "Geography" 的信息是正确的。它不适用于 TVP。
将此作为备选答案放在这里,以防万一其他人遇到此问题并发现它有用,并且为了完整起见,将尽可能多的信息包含在一个地方。
如果您根本没有在您的 C# 代码中使用 SqlGeometries(我只是包含了纯粹用于发送数据库值的库),那么坚持使用 WKT 似乎更有效。
最明确的方法是按照 Jon Bellamy 在上面接受的答案中建议的那样。然而,有一种更短的方式似乎表现更好。基本上,数据库会将有效的 WKT 值隐式转换为存储过程中的几何图形。
示例:
Table类型
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 格式。
我也试图找出一种扩展数据表的方法,但没有成功。但是,我确实找到了一个使用自定义迭代的好选择,它可能比数据表更快并且更灵活。
检查这些链接:
A detailed explanation of how to use custom iterators to pass a table valued param (TVP) to SQL server sproc
An example using SQL geometry in a TVP
值得注意的是,这种方法比 WKT 方法占用的数据库更少,因为它不需要数据库将输入转换为可用格式。
我正在尝试将一系列 SqlGeometry
值保存到 SQL Server 2008 数据库。
基本上我在 SQL 服务器存储过程中有一个表类型,如下所示:
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<BOSpatial> 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
列,它因错误消息而失败:
An exception of type 'System.ArgumentException' occurred in System.Data.dll but was not handled in user code
Additional information: The type of column 'geom' is not supported. The type is 'SqlGeometry'
这对我来说没有任何意义,因为我在文档中阅读的内容似乎支持该数据类型。
有什么想法吗?
编辑:
下面的评论和我链接的文章:https://viswaug.wordpress.com/2008/09/29/inserting-spatial-data-in-sql-server-2008/ 似乎建议我需要将 SqlGeometry
的数据类型更改为 SqlDbType.Udt
。遗憾的是,当我使用数据表时,我无法定义 UdtTypeName = “GEOMETRY”;
,因为这是在参数上设置的。
自从对您的问题作了简短评论后,我就有机会充分了解这些选项。目前看来(甚至尝试使用 .NET 4.6 和 SQL 2014)在为 [=22] 定义列时无法将 SqlGeography
OR SqlGeometry
设置为 typeof()
参数=].为绝对清楚起见,您可以在 .NET 中执行此操作,甚至填充它,但您无法随后将 table 作为 TVP 传递给存储过程。
有两种选择。
选项 1. 以 WKT 格式传递值。
按如下方式定义您的 table 类型。
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 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 格式传递值。
按如下方式定义您的 table 类型。
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 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%。这将因数据的复杂性和设置而异。
当您的存储过程具有 [Geometry] 或 [Geography] 类型的参数时,您找到的有关将参数的 UdtTypeName
指定为 "Geometry" / "Geography" 的信息是正确的。它不适用于 TVP。
将此作为备选答案放在这里,以防万一其他人遇到此问题并发现它有用,并且为了完整起见,将尽可能多的信息包含在一个地方。
如果您根本没有在您的 C# 代码中使用 SqlGeometries(我只是包含了纯粹用于发送数据库值的库),那么坚持使用 WKT 似乎更有效。
最明确的方法是按照 Jon Bellamy 在上面接受的答案中建议的那样。然而,有一种更短的方式似乎表现更好。基本上,数据库会将有效的 WKT 值隐式转换为存储过程中的几何图形。
示例:
Table类型
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 格式。
我也试图找出一种扩展数据表的方法,但没有成功。但是,我确实找到了一个使用自定义迭代的好选择,它可能比数据表更快并且更灵活。
检查这些链接:
A detailed explanation of how to use custom iterators to pass a table valued param (TVP) to SQL server sproc
An example using SQL geometry in a TVP
值得注意的是,这种方法比 WKT 方法占用的数据库更少,因为它不需要数据库将输入转换为可用格式。