2009-03-25 21 views
29

¿Cómo puedo/cuál es la mejor manera de hacer inserciones de bases de datos masivas?¿Cuál es la mejor manera de insertar inserciones de bases de datos a granel desde C#?

En C#, estoy iterando sobre una colección y llamando a un procedimiento almacenado de inserción para cada elemento de la colección.

¿Cómo envío todos los datos en una llamada a la base de datos?

E.g. Supongamos que tengo una lista de personas (List<Person>) que contiene 10 elementos. Actualmente estoy llamando al proceso de InsertPerson almacenado 10 veces. Me gustaría reducir esto a 1 llamada.

estoy usando MS SQL Server 2005.

+0

Sin duda la salida [ esta pregunta] (http://stackoverflow.com/questions/629455/how-should-i-optimize-multiple-calls-in-my-net-code-to-a-trivial-stored-procedur). La respuesta de los votos más votados (no, no es mía: p) presenta una solución muy elegante para este problema exacto. – Brann

+0

gracias por hacer esta pregunta –

Respuesta

1

un volcado de datos a un tubo delimitado (o algo más si los datos tienen tuberías en ella) archivo de texto y utilizar Bulk Insert.

+0

El método debe ser programático y rápido, sin disco de E/S. Gracias. –

+0

Puedes hacerlo usando MemoryStream para evitar File I/O, pero probablemente solo estás haciendo una implementación repetitiva de SQLBulkCopy. Ver mi comentario a la respuesta de @Mark Gavell. –

2

Puede construir un BLOB (imagen) y enviarlo como un parámetro a un procedimiento almacenado. Dentro del procedimiento almacenado, puede buscar todos los elementos utilizando subcadena().

1

Puede actualizar con un documento Xml, Sql 2005 funciona muy bien con ellos. Un nodo por fila, pero solo un parámetro para Xml.

24

Bueno, 10 elementos no es lo que llamo a granel, pero para conjuntos más grandes, SqlBulkCopy es tu amigo. Todo lo que necesita hacer es alimentarlo ya sea a DataTable o IDataReader (mi opción preferida, porque me gusta la transmisión de API). Hice algo similar here (puede ignorar el lado xml - solo la subclase SimpleDataReader).

+0

Marc: ¿sabes lo que hay debajo de SqlBulkCopy? –

+0

+1 para "transmisión de APIS". @Jason, mi suposición es que SqlBulkCopy envuelve la declaración de inserción BULK: http://technet.microsoft.com/en-us/library/ms187042.aspx –

+0

Lo sentimos, fue AFK, es esencialmente la misma API que "bcp", etc. - pero directamente a través de un flujo TDS (no envuelve un exe, o similar). –

1

crear un documento XML que contiene todos los elementos que se insertan. Luego, dentro de un procedimiento almacenado, use el soporte TSQL xml (OPENXML) para leer todos los datos del documento XML e insertarlo en sus tablas con la esperanza de insertar una declaración para cada tabla.

Sin embargo, si solo está insertando datos en una sola tabla y no necesita ninguna lógica de base de datos, ¿por qué no utilizar SqlBulkCopy?

+0

No necesita usar OPENXML con SQL 2005, solo use el tipo de datos XML incorporado. –

+0

@ Adam, OPENXML sigue siendo útil si solo está utilizando el XML como forma de transportar filas. Sin embargo, los tipos de datos XML de SQL 2005 son muy útiles si desea almacenar el XML en la base de datos. –

26

CsharperGuyInLondon, aquí hay un ejemplo simple de código 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

Aprecio este código de muestra. Me ahorraste mucho tiempo. insertar iterativamente 17k (32 columnas por fila) lleva aproximadamente 200 segundos en mi configuración de red. Usar este método toma menos de 10 segundos. –

+0

Algunas ideas Ya no es necesario crear un Objeto DataColoumn, puede pasar el nombre de la columna y escribir directamente en Add Function now Add ("id_state", typeof (int)); También el diseño de la tabla debe coincidir con el de la tabla en sql. Orden de la columna también – R2D2

1

Re la solución para SqlBulkCopy, he creado una clase que lleva Datatable o una List<T> y un tampón tamaño (CommitBatchSize). Convertirá la lista en una tabla de datos utilizando una extensión (en la segunda clase).

Funciona muy rápido. En mi PC, puedo insertar más de 10 millones de registros complicados en menos de 10 segundos.

Aquí es la clase:

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

}

Aquí se muestra un ejemplo cuando quiero insertar una lista de mi objeto personalizado List<PuckDetection> (ListDetections):

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