2009-03-25 16 views
29

Come faccio a/qual è il modo migliore per inserire inserimenti di database collettivi?Qual è il modo migliore per eseguire il bulk degli inserimenti del database da C#?

In C#, sto iterando su una raccolta e chiamando una procedura di inserimento memorizzato per ciascun elemento nella raccolta.

Come si inviano tutti i dati in una chiamata al database?

E.g. dire che ho una lista di persone (List<Person>) contenente 10 articoli. Al momento sto chiamando il processo memorizzato InsertPerson per 10 volte. Vorrei ridurre questo a 1 chiamata.

sto usando MS SQL Server 2005.

+0

Sicuramente controllare [ questa domanda] (http://stackoverflow.com/questions/629455/how-should-i-optimize-multiple-calls-in-my-net-code-to-a-trivial-stored-procedur). La risposta in topvot (no, non è mia: p) presenta una soluzione molto elegante a questo esatto problema. – Brann

+0

grazie per aver chiesto questa domanda –

risposta

1

Dump i dati ad un tubo delimitato (o qualcosa d'altro se i dati ha tubi in esso) file di testo e utilizzare Bulk Insert.

+0

Il metodo deve essere programmatico e veloce - nessun I/O del disco. Grazie. –

+0

È possibile eseguire l'operazione utilizzando MemoryStream per evitare l'I/O file, ma probabilmente si sta solo eseguendo l'implementazione dello standard di installazione SQLBulkCopy. Vedi il mio commento alla risposta di @Mark Gavell. –

2

È possibile creare un BLOB (immagine) e inviarlo come parametro a una stored procedure. All'interno della stored procedure, è possibile recuperare tutti gli elementi utilizzando la sottostringa().

1

È possibile aggiornare con un documento Xml, Sql 2005 funziona molto bene con loro. Un nodo per riga, ma solo un parametro per Xml.

24

Bene, 10 elementi non sono quello che io chiamo bulk, ma per set più grandi, SqlBulkCopy è tuo amico. Tutto quello che devi fare è alimentarlo sia a DataTable o a IDataReader (la mia opzione preferita, 'cos mi piacciono le API di streaming). Ho fatto qualcosa di simile here (puoi ignorare il lato xml - sottoclassi semplicemente SimpleDataReader).

+0

Marc: sai cosa c'è sotto il cofano di SqlBulkCopy? –

+0

+1 per "streaming APIS". @Jason, suppongo che SqlBulkCopy esegua il wrapping dell'istruzione di inserimento BULK: http://technet.microsoft.com/en-us/library/ms187042.aspx –

+0

Mi dispiace - era AFK - è essenzialmente la stessa API di "bcp" ecc. - ma direttamente tramite un flusso TDS (non avvolge un exe o simile). –

1

creare un documento XML che contiene tutti gli elementi da inserire. Quindi, all'interno di una procedura memorizzata, utilizzare il supporto TSML xml (OPENXML) per leggere tutti i dati dal documento XML e inserirlo nelle tabelle con speranza un'istruzione di inserimento per ogni tabella.

Tuttavia, se si inseriscono dati in una singola tabella e non è necessaria alcuna logica laterale del database, perché non utilizzare SqlBulkCopy?

+0

Non è necessario utilizzare OPENXML con SQL 2005, è sufficiente utilizzare il tipo di dati XML integrato. –

+0

@Adam, OPENXML è ancora utile se si utilizza semplicemente l'XML come metodo per trasportare le righe. Tuttavia, i tipi di dati XML di SQL 2005 sono molto utili se si desidera archiviare l'XML nel database. –

26

CsharperGuyInLondon, ecco un semplice esempio di codice SqlBulkCopy:

using System.Data.SqlClient; 

DataTable table = new DataTable("States"); 
// construct DataTable 
table.Columns.Add(new DataColumn("id_state", typeof(int))); 
table.Columns.Add(new DataColumn("state_name", typeof(string))); 

// note: if "id_state" is defined as an identity column in your DB, 
// row values for that column will be ignored during the bulk copy 
table.Rows.Add("1", "Atlanta"); 
table.Rows.Add("2", "Chicago"); 
table.Rows.Add("3", "Springfield"); 

using(SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) 
{ 
    bulkCopy.BulkCopyTimeout = 600; // in seconds 
    bulkCopy.DestinationTableName = "state"; 
    bulkCopy.WriteToServer(table); 
} 
+1

Apprezzo questo codice di esempio. Mi ha salvato un sacco di tempo. l'inserimento iterativo di record 17k (32 colonne per riga) richiede circa 200 secondi nelle impostazioni di rete. L'utilizzo di questo metodo richiede meno di 10 secondi. –

+0

Alcune idee Non è più necessario creare un oggetto DataColoumn, è possibile passare il nome della colonna e digitare direttamente nella funzione Aggiungi ora Aggiungi ("id_state", typeof (int)); Anche il layout della tabella deve corrispondere a quello della tabella in sql. Anche l'ordine delle colonne – R2D2

1

Re la soluzione per SqlBulkCopy, ho creato una classe che prende Datatable o un List<T> e un buffer dimensione (CommitBatchSize). Converte l'elenco in una tabella dati utilizzando un'estensione (nella seconda classe).

Funziona molto velocemente. Sul mio PC, sono in grado di inserire più di 10 milioni di record complicati in meno di 10 secondi.

Qui è la classe:

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Data.SqlClient; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

namespace DAL 
{ 

public class BulkUploadToSql<T> 
{ 
    public IList<T> InternalStore { get; set; } 
    public string TableName { get; set; } 
    public int CommitBatchSize { get; set; }=1000; 
    public string ConnectionString { get; set; } 

    public void Commit() 
    { 
     if (InternalStore.Count>0) 
     { 
      DataTable dt; 
      int numberOfPages = (InternalStore.Count/CommitBatchSize) + (InternalStore.Count % CommitBatchSize == 0 ? 0 : 1); 
      for (int pageIndex = 0; pageIndex < numberOfPages; pageIndex++) 
       { 
        dt= InternalStore.Skip(pageIndex * CommitBatchSize).Take(CommitBatchSize).ToDataTable(); 
       BulkInsert(dt); 
       } 
     } 
    } 

    public void BulkInsert(DataTable dt) 
    { 
     using (SqlConnection connection = new SqlConnection(ConnectionString)) 
     { 
      // make sure to enable triggers 
      // more on triggers in next post 
      SqlBulkCopy bulkCopy = 
       new SqlBulkCopy 
       (
       connection, 
       SqlBulkCopyOptions.TableLock | 
       SqlBulkCopyOptions.FireTriggers | 
       SqlBulkCopyOptions.UseInternalTransaction, 
       null 
       ); 

      // set the destination table name 
      bulkCopy.DestinationTableName = TableName; 
      connection.Open(); 

      // write the data in the "dataTable" 
      bulkCopy.WriteToServer(dt); 
      connection.Close(); 
     } 
     // reset 
     //this.dataTable.Clear(); 
    } 

} 

public static class BulkUploadToSqlHelper 
{ 
    public static DataTable ToDataTable<T>(this IEnumerable<T> data) 
    { 
     PropertyDescriptorCollection properties = 
      TypeDescriptor.GetProperties(typeof(T)); 
     DataTable table = new DataTable(); 
     foreach (PropertyDescriptor prop in properties) 
      table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType); 
     foreach (T item in data) 
     { 
      DataRow row = table.NewRow(); 
      foreach (PropertyDescriptor prop in properties) 
       row[prop.Name] = prop.GetValue(item) ?? DBNull.Value; 
      table.Rows.Add(row); 
     } 
     return table; 
    } 
} 

}

Ecco un esempio quando voglio inserire una lista dei miei oggetti personalizzati List<PuckDetection> (ListDetections):

var objBulk = new BulkUploadToSql<PuckDetection>() 
{ 
     InternalStore = ListDetections, 
     TableName= "PuckDetections", 
     CommitBatchSize=1000, 
     ConnectionString="ENTER YOU CONNECTION STRING" 
}; 
objBulk.Commit(); 
Problemi correlati