Переход от словаря <int, StringBuilder> к табличному параметру SqlParameter. Как? - PullRequest
2 голосов
/ 12 сентября 2009

У меня есть код, который имеет словарь, определенный как:

Dictionary<int, StringBuilder> invoiceDict = new Dictionary<int, StringBuilder>();

Каждое значение в каждой KeyValuePair словаря на самом деле представляет собой три отдельных значения, которые в настоящее время создаются следующим образом:

invoiceDict.Add(pdfCount+i, new StringBuilder(invoiceID.Groups[1].ToString() + "|" + extractFileName + "|" + pdfPair.Key));

Как видите, эти три значения разделены знаком '|'. Количество «строк» ​​в словаре может варьироваться от 600 до 1200. Я хотел бы использовать параметр с табличным значением, чтобы получить все это в моей БД SQL Server 2008 за одну операцию.

Табличный параметр определяется следующим образом:

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;

Спасибо.

Ответы [ 5 ]

1 голос
/ 24 февраля 2015

И @narayanamarthi, и @ChrisGessler находятся на правильном пути в отношении использования интерфейса IEnumerable<SqlDataRecord> вместо DataTable. Копирование коллекции в DataTable - это просто потеря процессора и памяти без какой-либо выгоды. Но итератор может быть отделен от коллекции.

Итак, некоторые заметки:

  • Не используйте AddWithValue при создании SqlParameter с. Это просто плохая практика
  • Не объединяйте 3 различных значения, которые вы хотите в TVP, в String или StringBuilder. Вся цель TVP - передать строго типизированную запись, поэтому сериализация полей в список CSV противоречит цели. Вместо этого используйте класс 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 в целом:

1 голос
/ 21 сентября 2009

Из-за проблемы с некачественной документацией, связанной с TVP (требуется больше, чем просто IEnumerable), и из-за необходимости разбирать содержимое словарей в любом случае, я решил перебрать словарь в DataTable, который, кажется, является предпочтительным объектом для кормить твп.

0 голосов
/ 26 февраля 2013

Вам необходимо реализовать IEnumerable<SqlDataRecord> на объекте пользовательской коллекции. В вашем случае вы должны изменить Dictionary<int, StringBuilder> на List<Invoice> или List<Tuple<int, string, int>>

Например:

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;
0 голосов
/ 26 октября 2009

Реализация интерфейса IEnumerable на классе, содержащем вашу коллекцию, и затем явная реализация метода GetEnumerator для IEnemerable будет служить цели. Есть статья на http://lennilobel.wordpress.com/2009/07/29/sql-server-2008-table-valued-parameters-and-c-custom-iterators-a-match-made-in-heaven/. Пожалуйста, пройдите через это

0 голосов
/ 12 сентября 2009

SqlBulkCopy отправит его за одну операцию, но вам нужно будет передать ему DataTable или IDataReader вместо Dictionary и вам также нужно будет указывать непосредственно на таблицу, вместо использования хранимой процедуры.

...