我的SQL Server存储过程带有表值参数
CREATE TYPE T_WORD AS TABLE
(
SWC_Index INT IDENTITY,
SWC_Value VARCHAR(MAX)
)
CREATE PROCEDURE SP_LOG
@i_msg VARCHAR(4000) ,
@i_word T_WORD READONLY
AS
BEGIN
SET IMPLICIT_TRANSACTIONS ON
declare @i int
SET @i = 1
while (@i <=(SELECT COUNT(*) FROM @i_word))
begin
INSERT INTO LG_REPORT
values(@i_msg,(select SWC_Value from @i_word where SWC_Index = @i))
end
IF @@TRANCOUNT > 0
COMMIT
END
我需要编写一个C#CLR包装器,只需在新连接中执行此过程。
C#代码看起来像这样
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
namespace SQLCLR
{
public partial class StoredProcedures
{
[Microsoft.SqlServer.Server.SqlProcedure]
public static void exec_SP_LOG(Object i_msg, Object i_word)
{
using (SqlConnection connection = new SqlConnection("context connection=true"))
{
SqlCommand Command = new SqlCommand();
SqlParameter i_msgParam = new SqlParameter("@i_msg", SqlDbType.VarChar);
SqlParameter i_wordParam = new SqlParameter("@i_word", SqlDbType.Structured);
i_wordParam.TypeName = "T_WORD";
i_msgParam.Value = i_msg;
i_wordParam.Value = i_word;
Command.Parameters.Add(i_msgParam);
Command.Parameters.Add(i_wordParam);
Command.CommandText = "exec SP_LOG @i_msg, @i_word";
Command.Connection = connection;
connection.Open();
Command.ExecuteNonQuery();
connection.Close();
}
}
}
}
将dll添加到SQL Server并创建过程
CREATE PROCEDURE [dbo].[exec_SP_LOG]
@i_msg sql_variant ,
@i_word sql_variant
AS
EXTERNAL NAME [SQLCLR].[SQLCLR.StoredProcedures].[exec_SP_LOG]
GO
我尝试执行此功能
declare @typ1 T_WORD
insert into @typ1(SWC_Value) values('djhgfj')
insert into @typ1(SWC_Value) values('dfhdf')
exec exec_SP_LOG 't1', @typ1
但是我收到以下错误
Msg 206,Level 16,State 2,Procedure exec_SP_LOG,Line 0
不兼容
操作数类型冲突:T_WORD与sql_variant
C#中的表值参数的数据类型看起来有问题。
问题是如何在C#中正确传递表值参数,因此从SQL Server调用此扩展过程的正确数据类型是什么。
非常感谢任何帮助。
答案 0 :(得分:3)
这段代码中有很多事情需要解决:
总体设计似乎更适合Microsoft SQL Server世界,而更多地适用于像PostgreSQL,甚至是Oracle(以及其他人?)的RDBMS。看起来您正在尝试使SQL Server像其他系统一样工作(或者至少期望它如此),但事实并非如此。我之所以这样说是因为您应该花一些时间阅读各种数据类型,事务处理以及基于集合和迭代/基于游标的方法。
如何在C#中正确传递表值参数
嗯,您i_wordParam
已正确设置为SqlDbType.Structured
。并且i_wordParam.TypeName
已正确设置为T_WORD
。仅供参考,设置TypeName
仅在即席查询时需要,而不是在通过CommandType.StoredProcedure
调用存储过程时。
因此,如果您已经拥有DataTable
,SqlDataReader
或已在C#代码中返回IEnumerable<SqlDataRecord>
的方法,那么您可以将其传递给SP_LOG
您目前正在设置该交互。
用于从SQL Server调用此扩展过程的正确数据类型是什么。
问题(至少是主要问题)是您无法将TVP传递给SQLCLR对象。正如CREATE PROCEDURE的MSDN页面中所述:
- 表值或光标数据类型不能用作参数。
TVP不是数据类型:它们是表类型。用户定义的表类型用于创建表变量。因此,虽然您确实将.NET Object
类型映射到T-SQL SQL_VARIANT
数据类型正确,但无法为SQL_VARIANT
分配表变量。这就是你得到操作数类型冲突的原因:T_WORD与sql_variant 错误不兼容。
正如@Damien_The_Unbeliever在对该问题的评论中已经指出的那样,你真的不应该为用户对象使用sp_
前缀,因为它是为系统保留的,并导致master
首先要检查该对象的数据库。
除非您有明确的理由这样做,否则我不会使用SET IMPLICIT_TRANSACTIONS ON
。您应该通过BEGIN TRAN
明确启动交易。如果在调用此proc之前已经启动了一个事务(即@@TRANCOUNT
在此proc开始时将> 0>那么最后的COMMIT
将导致错误{{ {1}}在过程结束时比在开始时少。
如果使用隐式事务的原因是在@@TRANCOUNT
为空时不启动事务,则可以通过在开头添加以下内容来退出proc:
@i_word
您没有任何错误检查或IF (NOT EXISTS(SELECT COUNT(*) FROM @i_word))
BEGIN
RETURN;
END;
逻辑。你应该使用以下内容:
ROLLBACK
关于BEGIN TRY
BEGIN TRAN;
... one or more SQL Statements
COMMIT;
END TRY
BEGIN CATCH
ROLLBACK;
;THROW; -- introduced in SQL Server 2012
---- Remove "THROW" and use the following if on SQL Server 2008
-- DECLARE @ErrMessage NVARCHAR(4000) = ERROR_MESSAGE();
-- RAISERROR(@ErrMessage, 16, 1);
-- RETURN;
END CATCH;
如果你需要一个WHILE循环(你不需要;稍后会详细介绍),那么你不想在循环中执行while (@i <=(SELECT COUNT(*) FROM @i_word))
,因为每次都会对它进行评估。
关于WHILE循环的结构:
你永远不会增加COUNT(*)
,因此你有一个无限循环。如果您需要WHILE循环,如果使用SQL Server 2008或更高版本则需要@i
,如果使用SQL Server 2005则需要SET @i += 1;
。
首先不需要WHILE循环。表值参数只是:表(虽然表变量,但仍然)。因此,他们可以加入等等。以下是一种更有效的方法:
SET @i = @i + 1;
插入时,您确实应该指定字段名称。因此INSERT INTO LG_REPORT
SELECT @i_msg, tmp.SWC_Value
FROM @i_word;
而非INSERT INTO LG_REPORT (column1, column2)
。不指定字段名称允许您向INSERT INTO LG_REPORT
添加字段而忘记更新此LG_REPORT
并且失败的情况。或者您可能没有正确顺序的字段。这样的东西。
虽然它不会导致错误,但您(每个人,真的)应该养成用分号结束每个查询的习惯。