124

我有一個MS SQL Server 2005數據庫。在一些過程中,我將表參數傳遞給一個存儲過程作爲nvarchar(用逗號分隔)並在內部劃分爲單個值。我將它添加到SQL命令的參數列表如下:如何將表值參數傳遞給存儲過程從.net代碼

cmd.Parameters.Add("@Logins", SqlDbType.NVarchar).Value = "jim18,jenny1975,cosmo"; 

我必須遷移數據庫到SQL Server 2008。我知道有表值參數,我知道如何在存儲過程中使用它們。但我不知道如何將其傳遞給SQL命令中的參數列表。 有沒有人知道Parameters.Add程序的正確語法?或者有另一種方法來傳遞這個參數?

+0

看看這個解決方案:EF中的表值參數存儲過程。 https://code.msdn.microsoft.com/Stored-Procedure-with-6c194514 – 2016-09-22 15:47:02

+0

在這種情況下,我通常連接字符串並將它們分離到服務器端,或者如果我有多個列,則傳遞甚至是xml。 Sql它處理XML時速度非常快。您可以嘗試所有方法並檢查處理時間,然後選擇最佳方法。 XML看起來像 ...。 Sql Server上的過程也很簡單。如果您需要更多信息,使用此方法,您總是可以向添加新屬性。 – 2016-12-22 07:51:36

回答

209

DataTable,DbDataReaderIEnumerable<SqlDataRecord>對象可用於填充每個MSDN文章Table-Valued Parameters in SQL Server 2008 (ADO.NET)的表值參數。

下面的例子說明使用一個DataTableIEnumerable<SqlDataRecord>

SQL代碼

CREATE TABLE dbo.PageView 
(
    PageViewID BIGINT NOT NULL CONSTRAINT pkPageView PRIMARY KEY CLUSTERED, 
    PageViewCount BIGINT NOT NULL 
); 
CREATE TYPE dbo.PageViewTableType AS TABLE 
(
    PageViewID BIGINT NOT NULL 
); 
CREATE PROCEDURE dbo.procMergePageView 
    @Display dbo.PageViewTableType READONLY 
AS 
BEGIN 
    MERGE INTO dbo.PageView AS T 
    USING @Display AS S 
    ON T.PageViewID = S.PageViewID 
    WHEN MATCHED THEN UPDATE SET T.PageViewCount = T.PageViewCount + 1 
    WHEN NOT MATCHED THEN INSERT VALUES(S.PageViewID, 1); 
END 

C#代碼

private static void ExecuteProcedure(bool useDataTable, 
            string connectionString, 
            IEnumerable<long> ids) 
{ 
    using (SqlConnection connection = new SqlConnection(connectionString)) 
    { 
     connection.Open(); 
     using (SqlCommand command = connection.CreateCommand()) 
     { 
      command.CommandText = "dbo.procMergePageView"; 
      command.CommandType = CommandType.StoredProcedure; 

      SqlParameter parameter; 
      if (useDataTable) { 
       parameter = command.Parameters 
           .AddWithValue("@Display", CreateDataTable(ids)); 
      } 
      else 
      { 
       parameter = command.Parameters 
           .AddWithValue("@Display", CreateSqlDataRecords(ids)); 
      } 
      parameter.SqlDbType = SqlDbType.Structured; 
      parameter.TypeName = "dbo.PageViewTableType"; 

      command.ExecuteNonQuery(); 
     } 
    } 
} 

private static DataTable CreateDataTable(IEnumerable<long> ids) 
{ 
    DataTable table = new DataTable(); 
    table.Columns.Add("ID", typeof(long)); 
    foreach (long id in ids) 
    { 
     table.Rows.Add(id); 
    } 
    return table; 
} 

private static IEnumerable<SqlDataRecord> CreateSqlDataRecords(IEnumerable<long> ids) 
{ 
    SqlMetaData[] metaData = new SqlMetaData[1]; 
    metaData[0] = new SqlMetaData("ID", SqlDbType.BigInt); 
    SqlDataRecord record = new SqlDataRecord(metaData); 
    foreach (long id in ids) 
    { 
     record.SetInt64(0, id); 
     yield return record; 
    } 
} 
+14

+1很好的例子。需要注意的是:發送'DataTable'作爲參數值,將'SqlDbType'設置爲'Structured'和'TypeName'作爲數據庫的UDT名稱。 – 2013-01-25 09:52:29

+7

如果你打算在一個循環中重用一個引用類型的實例(在你的例子中是SqlDataRecord),請在這個特定的實例中添加一條評論,說明爲什麼它是安全的。 – 2016-01-21 11:42:23

+2

這段代碼是錯誤的:空值表參數的值應該設置爲null。如果給定一個空的'ids'參數''CreateSqlDataRecords'永遠不會返回'null'。 – 2016-03-13 23:09:02

25

繼瑞安的答案,你會還需要設置DataColumnOrdinal財產如果你正在處理table-valued parameter列其序號是不是按字母順序。

舉個例子,如果你有在SQL用作參數如下表值:

CREATE TYPE NodeFilter AS TABLE (
    ID int not null 
    Code nvarchar(10) not null, 
); 

您將需要訂購在C#中的列這樣:

table.Columns["ID"].SetOrdinal(0); 
// this also bumps Code to ordinal of 1 
// if you have more than 2 cols then you would need to set more ordinals 

如果你不這樣做,你會得到一個解析錯誤,無法將nvarchar轉換爲int。

13

通用

public static DataTable ToTableValuedParameter<T, TProperty>(this IEnumerable<T> list, Func<T, TProperty> selector) 
    { 
     var tbl = new DataTable(); 
     tbl.Columns.Add("Id", typeof(T)); 

     foreach (var item in list) 
     { 
      tbl.Rows.Add(selector.Invoke(item)); 

     } 

     return tbl; 

    } 
+0

你能否讓我知道我作爲參數傳遞了什麼? Func 選擇器?不能僅僅是tbl.Rows.Add(item)而不需要該參數。 – GDroid 2015-03-18 20:59:31

+0

selector.Invoke(item)選擇項目上的屬性大多數情況下它的一個int,但它也允許你選擇一個字符串屬性 – Martea 2015-03-20 16:44:11

+0

你可以請提供一個例子,我如何把選擇器放在那裏?我有一個列表傳遞給存儲過程... – GDroid 2015-03-23 03:31:28

5

與它合作最徹底的方法。假設你的表被稱爲「dbo.tvp_Int」整數列表(自定義爲自己的表型)

創建此擴展方法...

public static void AddWithValue_Tvp_Int(this SqlParameterCollection paramCollection, string parameterName, List<int> data) 
{ 
    if(paramCollection != null) 
    { 
     var p = paramCollection.Add(parameterName, SqlDbType.Structured); 
     p.TypeName = "dbo.tvp_Int"; 
     DataTable _dt = new DataTable() {Columns = {"Value"}}; 
     data.ForEach(value => _dt.Rows.Add(value)); 
     p.Value = _dt; 
    } 
} 

現在你可以在一個添加表值參數只需通過這樣做:

cmd.Parameters.AddWithValueFor_Tvp_Int("@IDValues", listOfIds); 
+1

如果paramCollection爲NULL,該怎麼辦?如何傳遞空類型? – Muflix 2016-08-31 12:53:52

+2

@Muflix不明確的是,擴展方法實際上對空實例起作用。因此,在方法頂部添加一個簡單的'if(paramCollection!= null)'檢查將會很好 – Rhumborl 2017-03-16 21:41:09

+1

更新的答案與初始-if-檢查 – 2017-03-25 02:53:35

相關問題