辞書から移動< int、StringBuilder>テーブル値のSqlParameterに。どうやって?
-
06-07-2019 - |
質問
辞書が次のように定義されているコードがあります
Dictionary<int, StringBuilder> invoiceDict = new Dictionary<int, StringBuilder>();
辞書の各KeyValuePairの各値は、実際には次のように現在作成されている3つの個別の値です。
invoiceDict.Add(pdfCount+i, new StringBuilder(invoiceID.Groups[1].ToString() + "|" + extractFileName + "|" + pdfPair.Key));
ご覧のとおり、3つの値は「|」で区切られています。 「行」の数辞書内の範囲は600〜1200です。テーブル値パラメーターを使用して、1回の操作でSQL Server 2008 DBのすべての値を取得します。
テーブル値パラメーターは次のように定義されます。
CREATE TYPE dbo.BatchSplitterInvoices AS TABLE
(
InvoiceID varchar(24),
NotePath varchar(512),
BatchID varchar(50)
)
CREATE PROCEDURE dbo.cms_createBatchSplitterInvoices (
@Invoices dbo.BatchSplitterInvoices READONLY,
@StaffID int
)
辞書からテーブル値パラメーターに渡すことができるものに到達するための最良の方法は何ですか?辞書以外のものを使用する必要がありますか?または、辞書を別のより良いデータ構造に解析する必要がありますか?
SqlParameter invoicesParam = cmd.Parameters.AddWithValue("@Invoices", invoiceDict.Values);
invoicesParam.SqlDbType = SqlDbType.Structured;
ありがとう。
解決
TVP(IEnumerableだけでなく、それ以上のものが必要です)を取り巻くやや劣ったドキュメントを発行し、とにかく辞書の内容を解析する必要があるため、辞書をループしてDataTableにすることを決定しました。 TVPをフィードします。
他のヒント
DataTableの代わりに IEnumerable&lt; SqlDataRecord&gt;
インターフェースを使用することに関して、@ narayanamarthiと@ChrisGesslerの両方が正しい軌道に乗っています。コレクションをDataTableにコピーすると、CPUとメモリが無駄に消費されるだけです。ただし、イテレータはコレクションから分離できます。
注意事項:
-
SqlParameter
の作成時にAddWithValue
を使用しないでください。それはただの悪い習慣です - TVPで必要な3つの異なる値をStringまたはStringBuilderに連結しないでください。 TVPの全体的な目的は、厳密に型指定されたレコードを渡すことであるため、フィールドをCSVリストにシリアル化すると目的が無効になります。代わりに、Chrisが推奨するInvoiceクラスを使用してください。
-
invoiceDict.Add(...)
をList&lt; Invoice&gt; .Add(new Invoice(...));
に置き換えます
-
コレクションを反復処理するメソッドを作成します。
private static IEnumerable<SqlDataRecord> SendRows(List<Invoice> Invoices) { SqlMetaData[] _TvpSchema = new SqlMetaData[] { new SqlMetaData("InvoiceID", SqlDbType.Int), new SqlMetaData("NotePath", SqlDbType.VarChar, 512), new SqlMetaData("BatchID", SqlDbType.VarChar, 50) }; SqlDataRecord _DataRecord = new SqlDataRecord(_TvpSchema); foreach(Invoice _Invoice in Invoices) { _DataRecord.SetInt32(0, _Invoice.Id); _DataRecord.SetString(1, _Invoice.NotePath); _DataRecord.SetString(2, _Invoice.BatchId); yield return _DataRecord; } }
-
次のようにパラメーターを宣言します。
SqlParameter _InvoiceParam = cmd.Parameters.Add("@Invoices", SqlDbType.Structured); _InvoiceParam.Value = SendRows(Invoices);
TVP全般に関して、次の2つの回答に追加のメモとリンクがあります。
SqlBulkCopy が送信します1つの操作ですが、Dictionary&lt; int、StringBuilder&gt;の代わりにDataTableまたはIDataReaderを渡す必要があります。また、ストアドプロシージャを使用する代わりに、テーブルを直接指す必要があります。
コレクションを保持するクラスにIEnumerableインターフェイスを実装し、IEnemerableのGetEnumeratorメソッドを明示的に実装することで目的を果たします。 http://lennilobel.wordpress.com/2009/07/29/sql-server-2008-table-valued-parameters-and-c-custom-iterators- a-match-made-in-heaven / 。これを実行してください
カスタムコレクションオブジェクトに IEnumerable&lt; SqlDataRecord&gt;
を実装する必要があります。あなたの場合、 Dictionary&lt; int、StringBuilder&gt;
を List&lt; Invoice&gt;
または List&lt; Tuple&lt; int、string、int&gt;&gt; <に変更する必要があります
例:
class Invoice
{
public int Id { get; set; }
public string NotePath { get; set; }
public int BatchId { get; set; }
}
class InvoiceCollection : List<Invoice>, IEnumerable<SqlDataRecord>
{
IEnumerator<SqlDataRecord> IEnumerable<SqlDataRecord>.GetEnumerator()
{
SqlDataRecord r - new SqlDataRecord(
new SqlMetaData("InvoiceId", SqlDataType.Int),
new SqlMetaData("NotePath", SqlDataType.VarChar),
new SqlMetaData("BatchId", SqlDataType.Int)
);
foreach(var item in this)
{
r.SetInt32(0, item.Id);
r.SetString(1, item.NotePath);
r.SetInt32(2, item.BatchId);
yield return r;
}
}
次に、カスタムリストを SqlParameter
に渡すだけです:
SqlCommand cmd = new SqlCommand("dbo.InvoiceInsUpd", connection);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter sqlParam = cmd.Parameters.AddWithValue("@Invoices", invoices);
sqlParam.SqlDbType = SqlDbType.Structured;