Переход от словаря<int, StringBuilder=""> К табличному параметру SqlParameter.Каким образом?

StackOverflow https://stackoverflow.com/questions/1413364

Вопрос

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

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;

Спасибо.

Это было полезно?

Решение

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

Другие советы

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

Итак, несколько заметок:

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

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

Реализация интерфейса 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/.Пожалуйста, пройдите через это

Вам необходимо реализовать 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;
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top