web-dev-qa-db-fra.com

Mise à jour par lot / suppression EF5

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:

  1. 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.

  2. 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?

41
Frank
  1. Il existe deux projets open source permettant cela: EntityFramework.Extended et Entity Framework Extensions . Vous pouvez également consulter discussion sur les mises à jour en masse sur le site de codeplex d'EF.
  2. L'insertion de 100 000 enregistrements via EF est en premier lieu une mauvaise architecture d'application. Vous devez choisir une technologie légère différente pour les importations de données. Même le fonctionnement interne d'EF avec un si grand record vous coûtera beaucoup de temps de traitement. Il n'y a actuellement aucune solution pour les insertions par lots pour EF mais il y a large discussion à propos de cette fonctionnalité sur le site de code plex d'EF.
36
Ladislav Mrnka

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.

21
Alex Klaus

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.

3
Jim Wooley

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.

2
John Zabroski
    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;
    }
1
Jack CQ

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 .

0
Lukas Winzenried

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);
}
0
adam0101