2012-10-05 35 views
40

¿Cuál es la mejor manera de tratar con las actualizaciones por lotes utilizando (Entity Framework) EF5? tengo 2 casos particulares que me interesan:Actualización/eliminación de lotes EF5

  1. Actualización de un campo (por ejemplo, UpdateDate) para obtener una lista (lista) de entre 100 y 100.000 Id de que la clave principal. Llamar a cada actualización por separado parece ser muy costoso y lleva mucho tiempo.

  2. Insertar muchos, también entre 100 y 100.000, de los mismos objetos (por ejemplo, Usuarios) de una sola vez.

¿Algún buen consejo?

Respuesta

35
  1. Hay dos proyectos de código abierto que permite a este: EntityFramework.Extended y Entity Framework Extensions. También puede consultar discussion sobre las actualizaciones masivas en el sitio Codeplex de EF.
  2. Insertar 100k registros a través de EF es en primer lugar una arquitectura de aplicación incorrecta. Debe elegir diferentes tecnologías livianas para la importación de datos. Incluso el funcionamiento interno de EF con un gran conjunto de registros le costará mucho tiempo de procesamiento. Actualmente no hay solución para las inserciones por lotes para EF, pero hay broad discussion sobre esta característica en el sitio de código plex de EF.
+1

¿Conoce apoyo v4 EF en EntityFramework.Extended? Y parece que Entity Framework Extensions es un proyecto inactivo (la última versión fue en 2010) –

+1

Existe una extensión para las inserciones ef bulk en codeplex http://stackoverflow.com/a/27983392/937411 –

+0

¿Sigue siendo importante el procesamiento por lotes? – Jordan

3

Es posible que no desee escucharlo, pero su mejor opción es no utilizar EF para operaciones masivas. Para actualizar un campo en una tabla de registros, use una instrucción Update en la base de datos (posiblemente llamada a través de un proceso almacenado asignado a una Función EF). También puede usar el método Context.ExecuteStoreQuery para emitir una instrucción Update en la base de datos.

Para inserciones masivas, su mejor opción es utilizar Bulk Copy o SSIS. EF requerirá un golpe separado a la base de datos para cada fila que se inserte.

21

que ver las siguientes opciones:

1. La manera más simple - crear su petición SQL por las manos y ejecutar a través ObjectContext.ExecuteStoreCommand

context.ExecuteStoreCommand("UPDATE TABLE SET FIELD1 = {0} WHERE FIELD2 = {1}", value1, value2); 

2. Utilice EntityFramework.Extended

context.Tasks.Update(
    t => t.StatusId == 1, 
    t => new Task {StatusId = 2}); 

3. Crea tu propia extensión para EF. Hay un artículo Bulk Delete donde se logró este objetivo al heredar la clase ObjectContext. Vale la pena echarle un vistazo. La inserción/actualización masiva se puede implementar de la misma manera.

+1

'bContext.Database.ExecuteSqlCommand()' en EF6? – Jess

+0

Use en su lugar ctx.Database.ExecuteSqlCommand – Ovis

2

Las inserciones masivas deben realizarse con la clase SqlBulkCopy. Por favor, vea preexistente Stackoverflow Q & A en la integración de los dos: SqlBulkCopy and Entity Framework

SqlBulkCopy es mucho más fácil de usar que bcp (utilidad de copia masiva de línea de comandos) o incluso ABIERTO conjunto de filas.

+0

No existe una actualización masiva con SqlBulkCopy, solo BulkInsert – Pleun

+0

Probablemente respondí la segunda mitad de la pregunta y accidentalmente ingresé "actualizaciones masivas" en lugar de "inserciones masivas". Fijo. –

0

Estoy de acuerdo con la respuesta aceptada de que ef es probablemente la tecnología incorrecta para inserciones masivas. Sin embargo, creo que vale la pena echarle un vistazo al EntityFramework.BulkInsert.

0

Esto es lo que he hecho con éxito:

private void BulkUpdate() 
{ 
    var oc = ((IObjectContextAdapter)_dbContext).ObjectContext; 
    var updateQuery = myIQueryable.ToString(); // This MUST be above the call to get the parameters. 
    var updateParams = GetSqlParametersForIQueryable(updateQuery).ToArray(); 
    var updateSql = [email protected]"UPDATE dbo.myTable 
         SET col1 = x.alias2 
         FROM dbo.myTable 
         JOIN ({updateQuery}) x(alias1, alias2) ON x.alias1 = dbo.myTable.Id"; 
    oc.ExecuteStoreCommand(updateSql, updateParams); 
} 

private void BulkInsert() 
{ 
    var oc = ((IObjectContextAdapter)_dbContext).ObjectContext; 
    var insertQuery = myIQueryable.ToString(); // This MUST be above the call to get the parameters. 
    var insertParams = GetSqlParametersForIQueryable(insertQuery).ToArray(); 
    var insertSql = [email protected]"INSERT INTO dbo.myTable (col1, col2) 
         SELECT x.alias1, x.alias2 
         FROM ({insertQuery}) x(alias1, alias2)"; 
    oc.ExecuteStoreCommand(insertSql, insertParams.ToArray()); 
}  

private static IEnumerable<SqlParameter> GetSqlParametersForIQueryable<T>(IQueryable<T> queryable) 
{ 
    var objectQuery = GetObjectQueryFromIQueryable(queryable); 
    return objectQuery.Parameters.Select(x => new SqlParameter(x.Name, x.Value)); 
} 

private static ObjectQuery<T> GetObjectQueryFromIQueryable<T>(IQueryable<T> queryable) 
{ 
    var dbQuery = (DbQuery<T>)queryable; 
    var iqProp = dbQuery.GetType().GetProperty("InternalQuery", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); 
    var iq = iqProp.GetValue(dbQuery, null); 
    var oqProp = iq.GetType().GetProperty("ObjectQuery", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); 
    return (ObjectQuery<T>)oqProp.GetValue(iq, null); 
} 
0
public static bool BulkDelete(string tableName, string columnName, List<object> val) 
    { 
     bool ret = true; 

     var max = 2000; 
     var pages = Math.Ceiling((double)val.Count/max); 
     for (int i = 0; i < pages; i++) 
     { 
      var count = max; 
      if (i == pages - 1) { count = val.Count % max; } 

      var args = val.GetRange(i * max, count); 
      var cond = string.Join("", args.Select((t, index) => $",@p{index}")).Substring(1); 
      var sql = $"DELETE FROM {tableName} WHERE {columnName} IN ({cond}) "; 

      ret &= Db.ExecuteSqlCommand(sql, args.ToArray()) > 0; 
     } 

     return ret; 
    }