Domanda

Ho un codice che ha un dizionario definito come:

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

Ogni valore in ogni KeyValuePair nel dizionario è in realtà tre valori separati attualmente creati come segue:

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

Come puoi vedere, i tre valori sono separati da un '|'. Il numero di "righe" nel dizionario può variare tra 600-1200. Vorrei utilizzare un parametro con valori di tabella per ottenere tutto ciò nel mio database SQL Server 2008 in un'unica operazione.

Il parametro con valori di tabella è definito come segue:

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 è il modo migliore per passare dal Dizionario a qualcosa che può essere passato nel parametro con valori di tabella? Dovrei usare qualcos'altro oltre al dizionario? O devo analizzare il Dizionario in un'altra struttura di dati migliore?

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

Grazie.

È stato utile?

Soluzione

A causa del problema con una documentazione piuttosto scadente che circonda i TVP (ha bisogno di qualcosa di più di IEnumerable) e dovendo comunque analizzare il contenuto dei dizionari, ho deciso di eseguire il ciclo del dizionario in una DataTable, che sembra essere l'oggetto preferito da alimentare un TVP.

Altri suggerimenti

Sia @narayanamarthi che @ChrisGessler sono sulla buona strada per quanto riguarda l'utilizzo dell'interfaccia IEnumerable < SqlDataRecord > invece di una DataTable. Copiare la raccolta su una DataTable significa semplicemente sprecare CPU e memoria senza alcun guadagno. Ma l'iteratore può essere separato dalla raccolta.

Quindi alcune note:

  • Non utilizzare AddWithValue durante la creazione di SqlParameter . È solo una brutta pratica
  • Non concatenare i 3 valori distinti desiderati in TVP in String o StringBuilder. L'intero scopo di un TVP è quello di trasmettere un record fortemente tipizzato, quindi serializzare i campi in un elenco CSV ne vanifica lo scopo. Usa invece la classe Invoice come raccomandato da Chris.
  • Sostituisci invoiceDict.Add (...) con Elenco < Invoice > .Add (new Invoice (...));
  • Crea un metodo per scorrere la raccolta:

    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;
      }
    }
    
  • Dichiara il parametro come segue:

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

Ho ulteriori note e collegamenti nelle seguenti due risposte per quanto riguarda i TVP in generale:

SqlBulkCopy lo invierà in un'operazione, ma dovrai passargli una DataTable o una IDataReader anziché un Dizionario < int, StringBuilder > e dovrai anche puntare direttamente alla tabella, invece di utilizzare una procedura memorizzata.

L'implementazione dell'interfaccia IEnumerable sulla classe che contiene la tua raccolta e quindi l'implementazione esplicita del metodo GetEnumerator per IEnemerable servirà allo scopo. C'è un articolo su http://lennilobel.wordpress.com/2009/07/29/sql-server-2008-table-valued-parameters-and-c-custom-iterators- un match-made-in-heaven / . Si prega di passare attraverso questo

Devi implementare IEnumerable < SqlDataRecord > su un oggetto di raccolta personalizzato. Nel tuo caso, dovresti cambiare il tuo Dictionary < int, StringBuilder > in List < Invoice > o List < Tuple < int, string, int > >

Ad esempio:

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

Quindi passa semplicemente l'elenco personalizzato a un SqlParameter :

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

SqlParameter sqlParam = cmd.Parameters.AddWithValue("@Invoices", invoices);
sqlParam.SqlDbType = SqlDbType.Structured;
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top