Question

J'ai un code dont le dictionnaire est défini comme suit:

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

Chaque valeur de chaque KeyValuePair du dictionnaire est en réalité constituée de trois valeurs distinctes, actuellement créées comme suit:

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

Comme vous pouvez le constater, les trois valeurs sont séparées par un '|'. Le nombre de " lignes " dans le dictionnaire peut aller de 600 à 1200. J'aimerais utiliser un paramètre table pour obtenir tout cela dans ma base de données SQL Server 2008 en une seule opération.

Le paramètre table est défini comme suit:

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
)

Quel est le meilleur moyen de passer du dictionnaire à quelque chose qui peut être passé dans le paramètre table? Devrais-je utiliser autre chose que le dictionnaire? Ou dois-je analyser le dictionnaire dans une autre, meilleure structure de données?

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

Merci.

Était-ce utile?

La solution

En raison de problèmes liés à la documentation un peu médiocre sur les points de vue télévisés (il faut plus que juste IEnumerable) et de la nécessité d’analyser le contenu des dictionnaires de toute façon, j’ai décidé de parcourir le dictionnaire en un objet DataTable, qui semble être l’objet préféré nourrir un TVP.

Autres conseils

@narayanamarthi et @ChrisGessler sont sur la bonne voie en ce qui concerne l'utilisation de l'interface IEnumerable < SqlDataRecord > au lieu d'un DataTable. Copier la collection sur un DataTable ne fait que gaspiller le processeur et la mémoire sans gain. Mais l'itérateur peut être séparé de la collection.

Donc quelques notes:

  • N'utilisez pas AddWithValue lors de la création de SqlParameter . C'est juste une mauvaise pratique
  • Ne concaténez pas les 3 valeurs distinctes souhaitées dans le TVP en un String ou un StringBuilder. Le but d'un TVP est de transmettre un enregistrement fortement typé afin que la sérialisation des champs dans une liste CSV annule le but. Utilisez plutôt la classe Facture recommandée par Chris.
  • Remplacez votre invoiceDict.Add (...) par List < Invoice > .Add (new Invoice (...));
  • .
  • Créez une méthode pour parcourir la collection:

    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;
      }
    }
    
  • Déclarez le paramètre comme suit:

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

J'ai des notes et des liens supplémentaires dans les deux réponses suivantes concernant les TVP en général:

SqlBulkCopy l'enverra une opération, mais vous devrez lui transmettre un DataTable ou un IDataReader au lieu d’un dictionnaire < int, StringBuilder > et vous aurez également besoin de pointer directement sur la table, au lieu d'utiliser une procédure stockée.

L'implémentation de l'interface IEnumerable sur la classe contenant votre collection, puis l'implémentation explicite de la méthode GetEnumerator pour IEnemerable serviront à cela. Il y a un article sur http://lennilobel.wordpress.com/2009/07/29/sql-server-2008-table-valued-parameters-and-c-custom-iterators- un match-made-in-heaven / . S'il vous plaît passer par cette

Vous devez implémenter IEnumerable < SqlDataRecord > sur un objet de collection personnalisé. Dans votre cas, vous devez modifier votre Dictionnaire < int, StringBuilder > en Lister < Facture > ou Lister < Tuple < int, chaine, int > ; / code>

Par exemple:

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

Ensuite, passez simplement la liste personnalisée à un SqlParameter :

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

SqlParameter sqlParam = cmd.Parameters.AddWithValue("@Invoices", invoices);
sqlParam.SqlDbType = SqlDbType.Structured;
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top