表值引數簡介及與 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 NULLBEGIN 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'哲學')ENDGO--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) )ENDGO--3. 建立測試儲存過程IF EXISTS(SELECT * FROM sys.procedures AS p WHERE p.[object_id]=OBJECT_ID('dbo.Proc_UpdateCategories'))BEGIN DROP PROC dbo.Proc_UpdateCategoriesENDGOCREATE PROCEDURE dbo.Proc_UpdateCategories (@tvpEditedCategories dbo.type_CategoryTable READONLY)ASBEGIN 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;ENDGO
二、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_CategoryTableinsert into @p1 values(1,N'計算機11')insert into @p1 values(2,N'計算機12')exec Proc_UpdateCategories @[email protected] --方法2.select CategoryID,N'計算機2'+CAST(CategoryID AS nvarchar(30)) AS CategoryName from dbo.Categoriesselect CategoryID,N'計算機2'+CAST(CategoryID AS nvarchar(30)) AS CategoryName from dbo.Categories declare @p1 dbo.type_CategoryTableinsert into @p1 values(1,N'計算機21')insert into @p1 values(2,N'計算機22')exec Proc_UpdateCategories @[email protected] --方法3.declare @p1 dbo.type_CategoryTableinsert into @p1 values(1,N'計算機31')insert into @p1 values(2,N'計算機32')exec Proc_UpdateCategories @[email protected]由上可見:在C#中無論採用哪種方式使用表值引數, 都是將值逐行插入到表值引數(其實相當於表變數,只是比表變數多了作為引數的功能)中,然後再傳給儲存過程。
---------------------
作者:吉普賽的歌
來源:CSDN
原文:https://blog.csdn.net/yenange/article/details/51488408
版權宣告:本文為博主原創文章,轉載請附上博文連結!