Pergunta

Eu tenho código que tem um dicionário definido como:

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

Cada valor em cada keyvaluepair O dicionário é na verdade três valores separados atualmente criados da seguinte forma:

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

Como você pode ver, os três valores são separados por um '|'. O número de "linhas" no dicionário pode variar entre 600-1200. Gostaria de usar um parâmetro com valor de tabela para obter tudo isso no meu SQL Server 2008 DB em uma operação.

O parâmetro com valor de tabela é definido da seguinte forma:

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
)

Qual é a melhor maneira de obter do dicionário para algo que pode ser transmitido para o param com valor de mesa? Devo usar outra coisa que o dicionário? Ou preciso analisar o dicionário em outra estrutura de dados melhor?

SqlParameter invoicesParam = cmd.Parameters.AddWithValue("@Invoices", invoiceDict.Values);
invoicesParam.SqlDbType = SqlDbType.Structured;

Obrigado.

Foi útil?

Solução

Devido a problemas com uma documentação um pouco ruim em torno dos TVPs (precisa de mais do que apenas Ienumerable), e tendo que analisar o conteúdo dos dicionários de qualquer maneira, decidi dar um loop pelo dicionário em um datatable, que parece ser o objeto preferido para alimentar um TVP .

Outras dicas

Tanto @Narayanamarthi quanto @ChrisGessler estão no caminho certo com relação ao uso do IEnumerable<SqlDataRecord> interface em vez de um datatable. Copiar a coleção para um DataTable está desperdiçando a CPU e a memória sem ganho. Mas o iterador pode ser separado da coleção.

Então, algumas notas:

  • Não use AddWithValue Ao criar SqlParameters. É apenas uma má prática
  • Não concatenule os 3 valores distintos que você deseja no TVP em uma string ou stringbuilder. Todo o objetivo de um TVP é passar em um recorde fortemente tipado, então serializar os campos em uma lista de CSV derrota o objetivo. Em vez disso, use a classe de fatura, conforme recomendado por Chris.
  • Substitua o seu invoiceDict.Add(...) com List<Invoice>.Add(new Invoice(...));
  • Crie um método para iterar sobre a coleção:

    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;
      }
    }
    
  • Declare o parâmetro da seguinte forma:

    SqlParameter _InvoiceParam = cmd.Parameters.Add("@Invoices", SqlDbType.Structured);
    _InvoiceParam.Value = SendRows(Invoices);
    

Tenho notas e links adicionais nas duas respostas a seguir em relação ao TVPS em geral:

Sqlbulkcopy Enviará em uma operação, mas você precisará passar por um datatable ou um idatarader em vez de um dicionáriou003Cint, StringBuilder> E você também precisará apontar diretamente para a tabela, em vez de usar um procedimento armazenado.

A implementação da interface ienumerable na aula que mantém sua coleção e, em seguida, implementando explicitamente o método Getenumerator para o Ienemerable servirá a propósito. Há um artigo em http://lennilobel.wordpress.com/2009/07/29/sql-sherver-2008-table-valued-parameters-and-c-custom-iterators-a--match-festado-inheaven/. Por favor, passe por isso

Você precisa implementar IEnumerable<SqlDataRecord> em um objeto de coleção personalizado. No seu caso, você deve mudar seu Dictionary<int, StringBuilder> para List<Invoice> ou List<Tuple<int, string, int>>

Por exemplo:

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

Em seguida, basta passar na lista personalizada para um SqlParameter:

SqlCommand cmd = new SqlCommand("dbo.InvoiceInsUpd", connection);
cmd.CommandType = CommandType.StoredProcedure;

SqlParameter sqlParam = cmd.Parameters.AddWithValue("@Invoices", invoices);
sqlParam.SqlDbType = SqlDbType.Structured;
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top