从 Dictionary<int, StringBuilder> 到表值 SqlParameter。如何?
-
06-07-2019 - |
题
我的代码的字典定义为:
Dictionary<int, StringBuilder> invoiceDict = new Dictionary<int, StringBuilder>();
字典中每个 KeyValuePair 中的每个 Value 实际上是当前创建的三个单独的值,如下所示:
invoiceDict.Add(pdfCount+i, new StringBuilder(invoiceID.Groups[1].ToString() + "|" + extractFileName + "|" + pdfPair.Key));
正如您所看到的,这三个值由“|”分隔。字典中的“行”数可以在 600-1200 之间。我想使用表值参数通过一次操作获取 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。
其他提示
@narayanamarthi 和 @ChrisGessler 在使用 IEnumerable<SqlDataRecord>
接口而不是数据表。将集合复制到 DataTable 只会浪费 CPU 和内存,却没有任何好处。但迭代器可以与集合分离。
所以一些注意事项:
- 不要使用
AddWithValue
创建时SqlParameter
s。这只是一个不好的做法 - 不要将 TVP 中所需的 3 个不同值连接到 String 或 StringBuilder。TVP 的全部目的是传递强类型记录,因此将字段序列化到 CSV 列表中就达不到目的。相反,请使用 Chris 推荐的 Invoice 类。
- 更换你的
invoiceDict.Add(...)
和List<Invoice>.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 的其他注释和链接:
SqlBulkCopy 将发送给它一个操作,但您需要传递一个DataTable或IDataReader而不是Dictionary&lt; int,StringBuilder&gt;并且您还需要直接指向表,而不是使用存储过程。
在持有集合的类上实现IEnumerable接口,然后为IEnemerable显式实现GetEnumerator方法将实现此目的。 http://lennilobel.wordpress.com/2009/07/29/sql-server-2008-table-valued-parameters-and-c-custom-iterators- A级比赛 - 做在天堂/ 。请仔细阅读
您需要在自定义集合对象上实现 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;