带有表值参数的C#CLR存储过程

时间:2014-08-27 22:16:29

标签: c# sql-server stored-procedures clr table-valued-parameters

我的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调用此扩展过程的正确数据类型是什么。

非常感谢任何帮助。

1 个答案:

答案 0 :(得分:3)

这段代码中有很多事情需要解决:

  1. 总体设计似乎更适合Microsoft SQL Server世界,而更多地适用于像PostgreSQL,甚至是Oracle(以及其他人?)的RDBMS。看起来您正在尝试使SQL Server像其他系统一样工作(或者至少期望它如此),但事实并非如此。我之所以这样说是因为您应该花一些时间阅读各种数据类型,事务处理以及基于集合和迭代/基于游标的方法。

  2.   

    如何在C#中正确传递表值参数

    嗯,您i_wordParam已正确设置为SqlDbType.Structured。并且i_wordParam.TypeName已正确设置为T_WORD。仅供参考,设置TypeName仅在即席查询时需要,而不是在通过CommandType.StoredProcedure调用存储过程时。

    因此,如果您已经拥有DataTableSqlDataReader或已在C#代码中返回IEnumerable<SqlDataRecord>的方法,那么您可以将其传递给SP_LOG您目前正在设置该交互。

  3.   

    用于从SQL Server调用此扩展过程的正确数据类型是什么。

    问题(至少是主要问题)是您无法将TVP传递给SQLCLR对象。正如CREATE PROCEDURE的MSDN页面中所述:

      
        
    • 表值或光标数据类型不能用作参数。
    •   

    TVP不是数据类型:它们是表类型。用户定义的表类型用于创建表变量。因此,虽然您确实将.NET Object类型映射到T-SQL SQL_VARIANT数据类型正确,但无法为SQL_VARIANT分配表变量。这就是你得到操作数类型冲突的原因:T_WORD与sql_variant 错误不兼容。

  4. 正如@Damien_The_Unbeliever在对该问题的评论中已经指出的那样,你真的不应该为用户对象使用sp_前缀,因为它是为系统保留的,并导致master首先要检查该对象的数据库。

  5. 除非您有明确的理由这样做,否则我不会使用SET IMPLICIT_TRANSACTIONS ON。您应该通过BEGIN TRAN明确启动交易。如果在调用此proc之前已经启动了一个事务(即@@TRANCOUNT在此proc开始时将> 0>那么最后的COMMIT将导致错误{{ {1}}在过程结束时比在开始时少。

    如果使用隐式事务的原因是在@@TRANCOUNT为空时不启动事务,则可以通过在开头添加以下内容来退出proc:

    @i_word
  6. 您没有任何错误检查或IF (NOT EXISTS(SELECT COUNT(*) FROM @i_word)) BEGIN RETURN; END; 逻辑。你应该使用以下内容:

    ROLLBACK
  7. 关于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)),因为每次都会对它进行评估。

  8. 关于WHILE循环的结构:

    你永远不会增加COUNT(*),因此你有一个无限循环。如果您需要WHILE循环,如果使用SQL Server 2008或更高版本则需要@i,如果使用SQL Server 2005则需要SET @i += 1;

  9. 首先不需要WHILE循环。表值参数只是:表(虽然表变量,但仍然)。因此,他们可以加入等等。以下是一种更有效的方法:

    SET @i = @i + 1;
  10. 插入时,您确实应该指定字段名称。因此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并且失败的情况。或者您可能没有正确顺序的字段。这样的东西。

  11. 虽然它不会导致错误,但您(每个人,真的)应该养成用分号结束每个查询的习惯。