entity-framework - update - entity framework plus
ActualizaciĆ³n/eliminaciĆ³n de lote EF5 (7)
¿Cuál es la mejor manera de lidiar con las actualizaciones por lotes usando (Entity Framework) EF5? Tengo 2 casos particulares que me interesan:
Actualización de un campo (por ejemplo, UpdateDate) para una lista (Lista) de entre 100 y 100.000 Id, que es la clave principal. Llamar a cada actualización por separado parece ser muy costoso y lleva mucho tiempo.
Insertar muchos, también entre 100 y 100.000, de los mismos objetos (por ejemplo, usuarios) de una sola vez.
¿Algún buen consejo?
- Hay dos proyectos de código abierto que permiten esto: EntityFramework.Extended y Entity Framework Extensions . También puede consultar la discussion sobre las actualizaciones masivas en el sitio codeplex de EF.
- 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 existe una solución para las inserciones por lotes para EF, pero existe una amplia discusión sobre esta característica en el sitio de códigos de EF.
Esto es lo que hice 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 = $@"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 = $@"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);
}
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 a EntityFramework.BulkInsert .
Las inserciones masivas deben hacerse utilizando la clase SqlBulkCopy. Consulte las Preguntas y Respuestas de preexistentes sobre la integración de los dos: SqlBulkCopy y Entity Framework
SqlBulkCopy es mucho más fácil de usar que bcp (utilidad de línea de comandos de Bulk Copy) o incluso OPEN ROWSET.
Puede que no quiera escucharlo, pero su mejor opción es no usar 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, la mejor opción es usar Bulk Copy o SSIS. EF requerirá un golpe separado a la base de datos para cada fila que se inserte.
Veo las siguientes opciones:
1. La forma más simple: crea tu solicitud de SQL a mano y ejecuta a través de ObjectContext.ExecuteStoreCommand
context.ExecuteStoreCommand("UPDATE TABLE SET FIELD1 = {0} WHERE FIELD2 = {1}", value1, value2);
2. Usar 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.
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;
}