Es muy común que en las aplicaciones sea necesario realizar procesos de actualizaciones masivas. Siempre está la tentación de usar EF para hacer estos procesos, ya que es muy fácil buscar y actualizar registros. Pero en el caso de EF el costo que se paga por toda la infraestructura que tiene es muy alto.
En el caso de procesos masivos, si hay que actualizar o borrar un registro, primero hay que traerlo (esto implica que EF mantenga el tracking con el ObjectStateManager) posteriormente hacer la actualización o borrado y por último realizar la actualización con el ObjectContext.
Resumiento, para realizar procesos batch lo mejor es NO USAR Entity Framework !!
Pero que opciones tengo entonces?
Voy a mostrar 2 alternativas de las muchas que hay, simulando que hay que leer un archivo .TXT de longitud fija y posteriormente hay que realizar una actualización de miles de registros en una tabla de la base de datos.
El escenario es que recibo un archivo con códigos de productos de la empresa y códigos de productos de proveedores externos. El proceso que hay que realizar es: leer el archivo y actualizar los códigos de de los proveedores externos en cada registro de los productos de la empresa.
Para esto usaremos como ayuda la excelente libreria FileHelpers realizada por Marcos Meli
El formato en que viene el archivo es el siguiente:
/// <summary>
/// Archivo de Unión de Códigos de Productos de la Empresa y Proveedores
/// </summary>
[FixedLengthRecord(FixedMode.AllowLessChars)]
public class UnionProductos
{
[FieldFixedLength(7)]
public int EmpresaCodigo;
[FieldFixedLength(7)]
public string Proveedor1Codigo;
[FieldFixedLength(7)]
public int Proveedor2Codigo;
}
El proceso de lectura con FileHelpers:
FileHelperEngine engine = new FileHelperEngine(typeof(UnionProductos));
UnionProductos[] UnionLeido = engine.ReadFile(nombreArchivo) as UnionProductos[];
Opción 1
Recorrer el array de Productos y ejecutar un Store Procedure con la actualización correspondiente:
string stringDeConexion = "XXXXXXXXXXXXXXXX";
using (SqlConnection conn = new SqlConnection(stringDeConexion))
{
conn.Open();
SqlCommand cmd = new SqlCommand("nombreStore", conn);
cmd.Parameters.Add("@empresaCodigo", SqlDbType.Int);
cmd.Parameters.Add("@proveedor1Codigo", SqlDbType.VarChar);
cmd.Parameters.Add("@proveedor2Codigo", SqlDbType.Int);
foreach (UnionProductos producto in UnionLeido)
{
cmd.Parameters[0].Value = producto.EmpresaCodigo;
cmd.Parameters[1].Value = producto.Proveedor1Codigo;
cmd.Parameters[2].Value = producto.Proveedor2Codigo;
cmd.ExecuteNonQuery();
}
}
Por supuesto el Store hace algo asi como:
UPDATE Productos
SET Proveedor1Codigo = @proveedor1Codigo,
Proveedor2Codigo = @proveedor2Codigo
WHERE Productos.Codigo = @empresaCodigo
Opción 2
Esta es la mas performante de las 3 opciones. El único problema es que solo es posible en SQL Server 2008.
Está implementada utilizando una Tabla como Parámetro y realizando toda la actualización mediante una sentencia SQL.
Veamos como hacelo:
Se crea un tipo de dato Table en SQLServer
CREATE TYPE UnionProductosTbl AS TABLE
(
EmpresaCodigo int,
Proveedor1Codigo varchar(7),
Proveedor2Codigo int
)
Se crea el Procedimiento Almacenado usando ese tipo como parámetro:
CREATE PROCEDURE ActualizarCodigosProveedores
@unionProductosTbl dbo.UnionProductosTbl READONLY
AS
BEGIN
UPDATE Productos p
SET p.Proveedor1Codigo = tbl.Proveedor1Codigo,
p.Proveedor2Codigo = tbl.Proveedor2Codigo
FROM Productos
INNER JOIN @unionProductosTbl tbl ON p.Codigo = tbl.empresaCodigo
END
Y el proceso en el código:
//Lectura del Archivo
FileHelperEngine engine = new FileHelperEngine(typeof(UnionProductos));
//Devuelve como DataTable
DataTable unionLeidoDT = engine.ReadFileAsDT(nombreArchivo);
//El proceso de actualización
string stringDeConexion = "XXXXXXXXXXXXXXXX";
using (SqlConnection conn = new SqlConnection(stringDeConexion))
{
conn.Open();
SqlCommand cmd = new SqlCommand("nombreStore", conn);
//Se define un parámetro de tipo Structured para que reciba un DataTable
SqlParameter par = cmd.Parameters.Add("@unionProductosTbl",
SqlDbType.Structured);
par.Value = unionLeidoDT;
cmd.ExecuteNonQuery();
}
Realizando el proceso con 17.000 registros los resultados fueron los siguientes (los tiempos son orientativos):
| Opción |
Tiempo |
|
Utilizando EF
|
8 minutos |
|
Utilizando ADO.NET nativo procesando registro a registro con un Store Procedure
|
4 minutos |
|
Utilizando un Store Procedure en SQL 2008 con una tabla como parámetro
|
4 segundos !! |
Para finalizar, la opción de realizar el proceso batch usando una tabla como parámetro de un Store es la mejor opción si la prioridad es la performance de la solución.