表值参数简介及与 C# 的交互

参考文档:点击打开链接   点击打开链接


在 SQL Server 2008 中引入表值参数之前,用于将多行数据传递到存储过程或参数化 SQL 命令的选项受到限制。 开发人员可以选择使用以下选项,将多个行传递给服务器:


1. 使用一系列单个参数表示多个数据列和行中的值。 使用此方法传递的数据量受所允许的参数数量的限制。 SQL Server 过程最多可以有 2100 个参数。 必须使用服务器端逻辑才能将这些单个值组合到表变量或临时表中以进行处理。
2. 将多个数据值捆绑到分隔字符串或 XML 文档中,然后将这些文本值传递给过程或语句。 此过程要求相应的过程或语句包括验证数据结构和取消捆绑值所需的逻辑。
3. 针对影响多个行的数据修改创建一系列的单个 SQL 语句,例如通过调用 SqlDataAdapter 的 Update 方法创建的内容。 可将更改单独提交给服务器,也可以将其作为组进行批处理。 不过,即使是以包含多个语句的批处理形式提交的,每个语句在服务器上还是会单独执行。

4. 使用 bcp 实用工具程序或 SqlBulkCopy 对象将很多行数据加载到表中。 尽管这项技术非常有效,但不支持服务器端处理,除非将数据加载到临时表或表变量中。

实际代码上, 我们采用的最多的是第2种, 第1种也有用到,其它两种比较少见。


表值参数为强类型,其结构会自动进行验证。 表值参数的大小仅受服务器内存的限制。


优点

表值参数具有更高的灵活性,在某些情况下,可比临时表或其他传递参数列表的方法提供更好的性能。表值参数具有以下优势:

  • 首次从客户端填充数据时,不获取锁。

  • 提供简单的编程模型。

  • 允许在单个例程中包括复杂的业务逻辑。

  • 减少到服务器的往返。

  • 可以具有不同基数的表结构。

  • 是强类型。

  • 使客户端可以指定排序顺序和唯一键。

限制

表值参数有下面的限制:

  • SQL Server 不维护表值参数列的统计信息。

  • 表值参数必须作为输入 READONLY 参数传递到 Transact-SQL 例程。不能在例程体中对表值参数执行诸如 UPDATE、DELETE 或 INSERT 这样的 DML 操作。

  • 不能将表值参数用作 SELECT INTO 或 INSERT EXEC 语句的目标。表值参数可以在 SELECT INTO 的 FROM 子句中,也可以在 INSERT EXEC 字符串或存储过程中。

表值参数与 BULK INSERT 操作

数据源

服务器逻辑

行数

最佳技术

服务器上带格式的数据文件

直接插入

< 1000

BULK INSERT

服务器上带格式的数据文件

直接插入

> 1000

BULK INSERT

服务器上带格式的数据文件

复杂

< 1000

表值参数

服务器上带格式的数据文件

复杂

> 1000

BULK INSERT

远程客户端进程

直接插入

< 1000

表值参数

远程客户端进程

直接插入

> 1000

BULK INSERT

远程客户端进程

复杂

< 1000

表值参数

远程客户端进程

复杂

> 1000

表值参数

下面我们来关注一下表值参数的应用及与C#的交互:

一、预备SQL:

--1. 创建测试表
IF OBJECT_ID('Categories') IS NULL
BEGIN
	CREATE TABLE Categories(
		CategoryID INT PRIMARY KEY,
		CategoryName nvarchar(50)	
	)
	INSERT INTO Categories(CategoryID,CategoryName) VALUES(1,N'计算机')
	INSERT INTO Categories(CategoryID,CategoryName) VALUES(2,N'哲学')
END
GO
--2. 创建表值参数类型
IF NOT EXISTS (SELECT * FROM sys.types AS t WHERE t.name='type_CategoryTable')
BEGIN
	CREATE TYPE dbo.type_CategoryTable AS TABLE( 
		CategoryID INT,
		CategoryName nvarchar(50) 
	)
END
GO
--3. 创建测试存储过程
IF EXISTS(SELECT * FROM sys.procedures AS p WHERE p.[object_id]=OBJECT_ID('dbo.Proc_UpdateCategories'))
BEGIN
	DROP PROC dbo.Proc_UpdateCategories
END
GO
CREATE PROCEDURE dbo.Proc_UpdateCategories (@tvpEditedCategories dbo.type_CategoryTable READONLY)
AS
BEGIN
	SET NOCOUNT ON
	UPDATE dbo.Categories
    SET Categories.CategoryName = ec.CategoryName
    FROM dbo.Categories INNER JOIN @tvpEditedCategories AS ec
    ON dbo.Categories.CategoryID = ec.CategoryID;
END
GO

二、C#测试代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Data.Common;
using Microsoft.SqlServer.Server;

namespace TableParametersTest
{
    class Program
    {
        //连接串
        //注:MultipleActiveResultSets=True => 允许一个连接多个 SqlDataReader (不推荐,仅为实现 SqlDataReader 作表值参数)
        static readonly string CONNSTRING = "Data Source=(local),2014;Initial Catalog=AdventureWorks2014;Integrated Security=True;MultipleActiveResultSets=True";

        static void Main(string[] args)
        {
            //1. 以 DataTable 作为表值参数
            DataTable dt = new DataTable();
            dt.Columns.Add(new DataColumn("CategoryID", typeof(int)));
            dt.Columns.Add(new DataColumn("CategoryName", typeof(string)));
            for (int i = 1; i <= 2; i++)
            {
                DataRow dr = dt.NewRow();
                dr["CategoryID"] = i;
                dr["CategoryName"] = "计算机1"+i.ToString();
                dt.Rows.Add(dr);
            }
            
            SqlParameter sp = new SqlParameter("@tvpEditedCategories", dt);
            sp.SqlDbType = SqlDbType.Structured;
            sp.TypeName = "dbo.type_CategoryTable";
            ExecuteNonQueryByProc("Proc_UpdateCategories", sp);

            //2. 以 SqlDataReader 作为表值参数 (不推荐)
            string sql = "select CategoryID,N'计算机2'+CAST(CategoryID AS nvarchar(30)) AS CategoryName from dbo.Categories";
            using (SqlConnection conn = new SqlConnection(CONNSTRING)) 
            {
                conn.Open();
                SqlCommand cmd = new SqlCommand(sql, conn);
                SqlDataReader sdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);

                SqlCommand cmd2 = new SqlCommand("Proc_UpdateCategories", conn);
                cmd2.CommandType = CommandType.StoredProcedure;
                SqlParameter tvpParam = cmd2.Parameters.AddWithValue("@tvpEditedCategories", sdr);
                tvpParam.SqlDbType = SqlDbType.Structured;
                tvpParam.TypeName = "dbo.type_CategoryTable";
                cmd2.ExecuteNonQuery();
            }

            //3. 以 IList<SqlDataRecord> 作为表值参数
            IList<SqlDataRecord> list = new List<SqlDataRecord>();
            for (int i = 1; i <= 2; i++)
            {
                SqlDataRecord record = new SqlDataRecord(
                    new SqlMetaData[] { 
                    new SqlMetaData("CategoryID", SqlDbType.Int),
                    new SqlMetaData("CategoryName", SqlDbType.NVarChar,50)
                });
                record.SetInt32(0, i);
                record.SetSqlString(1, "计算机3"+i.ToString());
                list.Add(record);
            }

            SqlParameter sp3 = new SqlParameter("@tvpEditedCategories", list);
            sp3.SqlDbType = SqlDbType.Structured;
            sp3.TypeName = "dbo.type_CategoryTable";
            ExecuteNonQueryByProc("Proc_UpdateCategories", sp3);

            Console.WriteLine("End");
            Console.Read();
        }

        static void ExecuteNonQueryByProc(string proc, params SqlParameter[] spArr) 
        {
            using (SqlConnection conn = new SqlConnection(CONNSTRING)) 
            {
                conn.Open();
                SqlCommand cmd = new SqlCommand(proc, conn);
                cmd.CommandType = CommandType.StoredProcedure;
                if (spArr!=null && spArr.Length > 0)
                {
                    cmd.Parameters.AddRange(spArr);
                }
                cmd.ExecuteNonQuery();
            }
        }
    }
}

C#运行时SQL Profiler跟踪到的脚本:

--方法1.
declare @p1 dbo.type_CategoryTable
insert into @p1 values(1,N'计算机11')
insert into @p1 values(2,N'计算机12')
exec Proc_UpdateCategories @tvpEditedCategories=@p1

--方法2.
select CategoryID,N'计算机2'+CAST(CategoryID AS nvarchar(30)) AS CategoryName from dbo.Categories
select CategoryID,N'计算机2'+CAST(CategoryID AS nvarchar(30)) AS CategoryName from dbo.Categories

declare @p1 dbo.type_CategoryTable
insert into @p1 values(1,N'计算机21')
insert into @p1 values(2,N'计算机22')
exec Proc_UpdateCategories @tvpEditedCategories=@p1

--方法3.
declare @p1 dbo.type_CategoryTable
insert into @p1 values(1,N'计算机31')
insert into @p1 values(2,N'计算机32')
exec Proc_UpdateCategories @tvpEditedCategories=@p1


由上可见:在C#中无论采用哪种方式使用表值参数, 都是将值逐行插入到表值参数(其实相当于表变量,只是比表变量多了作为参数的功能)中,然后再传给存储过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值