.netコードからストアドプロシージャにテーブル値パラメーターを渡す方法


171

SQL Server 2005データベースがあります。いくつかのプロシージャでは、nvarchar(カンマで区切られた)ストアドプロシージャに渡し、内部で単一の値に分割するテーブルパラメータがあります。これをSQLコマンドパラメータリストに次のように追加します。

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

データベースをSQL Server 2008に移行する必要があります。テーブル値パラメーターがあり、それらをストアドプロシージャで使用する方法を知っています。しかし、SQLコマンドでパラメーターリストに渡す方法はわかりません。

誰かがParameters.Add手順の正しい構文を知っていますか?または、このパラメーターを渡す別の方法はありますか?


このソリューションを確認してください:EFのテーブル値パラメーターを持つストアドプロシージャ。 code.msdn.microsoft.com/Stored-Procedure-with-6c194514
Carl Prothman '22

このような場合、通常は文字列を連結してサーバー側で分割するか、複数の列がある場合はxmlも渡します。SQLは、XMLの処理時に非常に高速です。すべての方法を試し、処理時間を確認してから、最適な方法を選択してください。XMLは<Items> <Item value = "sdadas" /> <Item value = "sadsad" />...</ Items>のようになります。SQL Serverでのプロセスも単純です。この方法を使用すると、詳細が必要な場合はいつでも<item>に新しい属性を追加できます。
Nițu Alexandru

4
@ NițuAlexandru、「SQLはXMLを処理するときに非常に高速です。」程遠い。
nothrow 2018

回答:


278

DataTableDbDataReaderまたはIEnumerable<SqlDataRecord>オブジェクトは、MSDNの記事ごとのテーブル値パラメータ移入するために使用することができますSQL Server 2008の(ADO.NET)で表値パラメータを

次の例は、DataTableまたはの使用を示していますIEnumerable<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;
    }
}

24
+1優れた例。持ち帰りは以下のとおりです。送信DataTable、パラメータ値として設定SqlDbTypeするStructuredTypeName、データベースUDT名にします。
lc。

10
参照型のインスタンスをループで再利用する場合(例ではSqlDataRecord)、この特定のインスタンスで安全に使用できる理由についてコメントを追加してください。
セーレンBoisen

2
このコードは間違っています。空のテーブル値パラメーターの値はに設定する必要がありますnull。空のパラメータが指定されている場合CreateSqlDataRecordsは返されません。nullids
ta.speot.is 2016年

4
@Crono:(DataTableまたはDataSet)Visual-Studioのドラッグ&ドロップ機能をサポートする必要があるため、これを実装するだけで、IComponentどの実装を実装するかを実装しIDisposableます。デザイナーを使用せずに手動で作成する場合、using破棄する(または-statement を使用する)理由はありません。したがって、これは「実装するすべてのものを破棄する」という黄金律の例外の1つですIDisposable
Tim Schmelter 16

2
@TimSchmelter経験則として、Disposeコード分​​析が警告を出さないようにするためだけである場合でも、常にメソッドを呼び出します。しかし、ベースDataSetDataTableインスタンスが使用されるこの特定のシナリオでは、呼び出しDisposeは何もしないことに同意します。
Crono 2016

31

さらにライアンの答えにあなたも設定する必要があります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に変換できませんでした。


15

ジェネリック

   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;

    }

パラメータとして何を渡すかを教えてください。Func <T、TProperty>セレクター?単にtbl.Rows.Add(item)にすることはできず、そのパラメーターは必要ありません。
GDroid 2015年

selector.Invoke(item)は、アイテムのプロパティを選択します。ほとんどの場合はそのintですが、文字列プロパティを選択することもできます
Martea

セレクターを配置する方法の例を教えていただけますか?ストアド
プロシージャ

guidList.ToTabledValuedParameter(x => x)、xはあなたの場合のGUIDなので、戻り値は、GUIDのリストを含む1つの列(ID)を持つDataTableになります
Martea

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;
   }
}

これで、次のようにするだけで、テーブル値パラメーターを1行でどこにでも追加できます。

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

1
paramCollectionがNULLの場合はどうなりますか?空の型を渡す方法は?
Muflix 2016

2
@Muflixあいまいなことに、拡張メソッドは実際にはnullインスタンスに対して機能します。したがってif(paramCollection != null)、メソッドの上部に簡単なチェックを追加することは問題ありません
Rhumborl 2017年

1
最初の-if-チェックで回答を更新
Qureshi

2
多分少し面倒ですが、私はシグネチャのIEnumerable代わりに使用します。これにより、リストだけでなく、List何でも渡すことができIEnumerableます。に固有の関数を使用Listしていないので、私たちIEnumerable
Francis Lord

Listを使用すると、省略形のdata.ForEach()を使用できます。それ以外の場合は、実際にforeachループを作成する必要があります。どちらでもかまいませんが、できるだけ短く書くことが好きです。
Shahzad Qureshi

0

このコードを使用して、タイプから適切なパラメーターを作成します。

private SqlParameter GenerateTypedParameter(string name, object typedParameter)
{
    DataTable dt = new DataTable();

    var properties = typedParameter.GetType().GetProperties().ToList();
    properties.ForEach(p =>
    {
        dt.Columns.Add(p.Name, Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType);
    });
    var row = dt.NewRow();
    properties.ForEach(p => { row[p.Name] = (p.GetValue(typedParameter) ?? DBNull.Value); });
    dt.Rows.Add(row);

    return new SqlParameter
    {
        Direction = ParameterDirection.Input,
        ParameterName = name,
        Value = dt,
        SqlDbType = SqlDbType.Structured
    };
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.