sábado, 10 de junio de 2017

Copia masiva de datos usando OracleBulkCopy y Array Binding

En algunas ocasiones en nuestros desarrollos nos encontramos con la necesidad de copiar o insertar una gran cantidad de registros a nuestra base de datos desde algún origen externo como lo puede ser un archivo u otra base de datos; En esta entrada les comparto dos formas en las que pueden realizar esta copia de información de manera eficiente


El poderoso método de copia masiva BulkCopy

No tiene rival cuando de copiar una gran cantidad de datos se trata, yo lo recomiendo cuando se trata de más de 10,000 registros e incluso menos si es que requieres que tu copia de datos sea rápida. Debes garantizar que los registros que copias no infrinjan alguna de las reglas de la tabla y solo se puede hacer la copia no se puede usar con sentencias sql.

public static void CopiaMasivao(List<Libro> listaLibros)
{
        OracleBulkCopy copiaMasiva = new OracleBulkCopy("tu cadena de conexion");
        
        copiaMasiva.BulkCopyTimeout = 600;
        copiaMasiva.DestinationTableName = "LIBRO";
        copiaMasiva.ColumnMappings.Add("ID", "ID");
        copiaMasiva.ColumnMappings.Add("TITULO", "TITULO");
        copiaMasiva.ColumnMappings.Add("ISBN", "ISBN");
        //El uso de mappings sólo se requiere cuando el origen y el destino tienen número u orden de
        //columnas/propiedades diferentes. 
     
        copiaMasiva.WriteToServer(listaLibros.ToDataTable());

       cmdInsert.Connection.Close()
}

//Método  que que permite convertir una IEnumerable (en este caso nuestra lista generica) en un DataTable 

  public static class IEnumerableExtensions
    {
      public static DataTable ToDataTable<T>(this IEnumerable<T> data)
            {
                PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
                var 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;
            }
        
    }

---------------------------------------------------------------------------------------------------------------------


El  poco documentado método de Array Binding (hasta donde se sólo disponible para Oracle)

Este método es muy efectivo lo recomiendo cuando se trata de menos de 10,000 registros o bien cuando no no se puede hacer uso de BulkCopy. La diferencia respecto al método de registro por registros que este método manda la información por bloques a la BD en lugar de enviar uno por uno, el tamaño del bloque es determinado por la propiedad BatchSieze. Es un excelente método de realizar no solo copia de datos sino de ejecutar instrucciones SQL con parámetros asociados.

public static void CopiaArray(List<Libro> listaLibros)
{
            string strSql = @"INSERT INTO LIBRO (ID,  TITULO,  ISBN) 
                                        VALUES  (:ID, :TITULO, :ISBN)";

            OracleCommand cmdInsert= new OracleCommand(strSql);
            cmdInsert.Connection = new OracleConnection("tu cadena de conexion");

            cmdInsert.Parameters.Add(":ID",OracleDbType.Int64);
            cmdInsert.Parameters.Add(":TITULO",OracleDbType.Varchar2,255);
            cmdInsert.Parameters.Add(":ISBN",OracleDbType.Int64);

            cmdInsert.Connection.Open();
            cmdInsert.Parameters["ID"].Value =  listaLibros.Select(x => x.ID).ToArray();
            cmdInsert.Parameters["TITULO"].Value = listaLibros.Select(x => x.Titulo).ToArray();
            cmdInsert.Parameters["ISBN"].Value =listaLibros.Select(x => x.ISBN).ToArray();;
            cmd.ArrayBindCount = listaLibros.Count;
          
            cmdInsert.ExecuteNonQuery();
            cmdInsert.Connection.Close()
}



El viejo y conocido método de registro por registro  

Ineficiente a la hora de copiar o insertar un numero importante de registros,  ya que por cada execute del comando se realiza un viaje a la BD con el respectivo costo que esto implica. Solo es recomendable cuando la inserción puede generar un error  y se requiere que continúe  con las demás inserciones 

public static void CopiaRegistroPorRegistro(List<Libro> listaLibros)
{
            string strSql = @"INSERT INTO LIBRO (ID,  TITULO,  ISBN) 
                                        VALUES  (:ID, :TITULO, :ISBN)";

            OracleCommand cmdInsert= new OracleCommand(strSql);
            cmdInsert.Connection = new OracleConnection("tu cadena de conexion");
            cmdInsert.Parameters.Add(":ID",OracleDbType.Int64);
            cmdInsert.Parameters.Add(":TITULO",OracleDbType.Varchar2,255);
            cmdInsert.Parameters.Add(":ISBN",OracleDbType.Int64);

            cmdInsert.Connection.Open();
            foreach (Libro libro  in listaLibros)
            {
                cmdInsert.Parameters["ID"].Value = libro.ID;
                cmdInsert.Parameters["TITULO"].Value = libro.Titulo;
                cmdInsert.Parameters["ISBN"].Value = libro.ISBN;

                cmdInsert.ExecuteNonQuery();
            }
            cmdInsert.Connection.Close()
}