Quelle est la meilleure façon de gérer les mises à jour par lots en utilisant (Entity Framework) EF5? J'ai 2 cas particuliers qui m'intéressent:
Mise à jour d'un champ (par exemple UpdateDate) pour une liste (liste) comprise entre 100 et 100.000 ID, dont la clé primaire. L'appel de chaque mise à jour séparément semble être trop coûteux et prend beaucoup de temps.
Insérer plusieurs, également entre les 100 et 100 000, des mêmes objets (par exemple, les utilisateurs) en une seule fois.
Un bon conseil?
Je vois les options suivantes:
1 . La manière la plus simple - créez votre requête SQL à la main et exécutez-la via ObjectContext.ExecuteStoreCommand
context.ExecuteStoreCommand("UPDATE TABLE SET FIELD1 = {0} WHERE FIELD2 = {1}", value1, value2);
2. Utilisez EntityFramework.Extended
context.Tasks.Update(
t => t.StatusId == 1,
t => new Task {StatusId = 2});
3. Faites votre propre extension pour EF. Il existe un article Bulk Delete où cet objectif a été atteint en héritant de la classe ObjectContext . Ça vaut le coup d'oeil. L'insertion/mise à jour en bloc peut être implémentée de la même manière.
Vous ne voudrez peut-être pas l'entendre, mais votre meilleure option est de ne pas utiliser EF pour les opérations en bloc. Pour mettre à jour un champ dans une table d'enregistrements, utilisez une instruction Update dans la base de données (éventuellement appelée via un proc stocké mappé sur une fonction EF). Vous pouvez également utiliser la méthode Context.ExecuteStoreQuery pour émettre une instruction Update dans la base de données.
Pour les encarts massifs, votre meilleur pari est d'utiliser la copie en bloc ou SSIS. EF nécessitera un accès distinct à la base de données pour chaque ligne insérée.
Les insertions en masse doivent être effectuées à l'aide de la classe SqlBulkCopy. Veuillez consulter les questions et réponses StackOverflow préexistantes sur l'intégration des deux: SqlBulkCopy et Entity Framework
SqlBulkCopy est beaucoup plus convivial que bcp (utilitaire de ligne de commande Bulk Copy) ou même OPEN ROWSET.
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;
}
Je suis d'accord avec la réponse acceptée selon laquelle ef est probablement la mauvaise technologie pour les inserts en vrac. Cependant, je pense qu'il vaut la peine de jeter un œil à EntityFramework.BulkInsert .
Voici ce que j'ai réussi:
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);
}