web-dev-qa-db-fra.com

Comment supprimer plusieurs lignes dans Entity Framework Core?

J'ai besoin de supprimer plusieurs lignes d'une base de données à l'aide d'Entity Framework Core.

Ce code ne fonctionne PAS:

foreach (var item in items)
{
    myCollection.Remove(item);
}

car j'obtiens une erreur "InvalidOperationException: la collection a été modifiée; l'opération d'énumération peut ne pas s'exécuter" après le premier objet. En d'autres termes, . Remove supprime un seul objet.

Entity Framework Core n'a PAS . RemoveRange, donc je n'ai aucune idée de comment effectuer cette opération.

Afin de préserver une compatibilité maximale avec les différents fournisseurs de bases de données, je préférerais NE PAS appeler context.Database.ExecuteSqlCommand ("supprimer de physical_table où ..."). Existe-t-il une solution appropriée? Merci!

14
Giorgio Borgo

car j'obtiens une erreur "InvalidOperationException: la collection a été modifiée; l'opération d'énumération peut ne pas s'exécuter" après le premier objet. En d'autres termes, .Remove supprime un seul objet.

Cela n'a rien à voir avec EF Core et, oui, .Remove() ne supprime qu'un seul objet. Cependant, vous essayez de modifier une collection que vous parcourez. Il il existe des moyens de le faire , mais ce n'est pas une bonne voie à suivre.

Entity Framework Core n'a PAS .RemoveRange, donc je ne sais pas comment effectuer cette opération.

Il existe certainement au moins quelques façons simples de supprimer plusieurs enregistrements dans EF Core. Et, EF Core a une méthode RemoveRange() - c'est une méthode sur DbSet<TEntity>, Voir ici dans la documentation de l'API (comme indiqué dans le commentaire ci-dessus).

Quelques options:

  1. Si myCollection est d'un type qui appartient à un DbSet<TEntity>, Un simple appel comme celui-ci fera l'affaire:

    _dbContext.MyEntities.RemoveRange(myCollection);
    _dbContext.SaveChanges();
    
  2. Si myCollection est en fait une propriété de navigation hors d'une entité que vous avez interrogée, vous pouvez appeler .Clear() sur la collection au lieu d'itérer et d'appeler .Remove().

    var myParentEntity = _dbContext.MyParentEntities
                             .Include(x => x.MyChildrenEntities)
                             .Single(x => x.Id == id);
    myParentEntity.MyChildrenEntities.Clear();
    _dbContext.SaveChanges();
    

Comme également indiqué ci-dessus, il y a beaucoup de contexte manquant sur votre question - un code plus complet devrait être publié. Je prends juste quelques coups de couteau dans le noir pour vous mettre en route avec EF Core!

16
steamrolla

Si vous souhaitez supprimer de nombreux éléments (en lire des centaines ou plus) sur un filtre arbitraire, le moyen le plus efficace serait une "suppression en bloc". EFCore.BulkExtensions permet cela. Vérifiez un exemple ci-dessous:

var toRemoveModels = DataAccess.ModelRepository.All
    .Where(m => m.Name.StartsWith("Added model"))
    .ToList();
DataAccess.ModelRepository.BulkDelete(toRemoveModels);

où l'implémentation réelle dans le contexte de la base de données est aussi simple que:

public void BulkDelete<TModel>(IList<TModel> entities) where TModel: class
{
    this.BulkDelete(entities, bulkConfig: null);
}

Cela générera un tas de requêtes, mais sera toujours plus efficace que d'émettre de nombreuses instructions DELETE:

SELECT [m].[Id], [m].[MakeId], [m].[Name], [m].[PriceInEur]
FROM [Model] AS [m]
WHERE [m].[Name] LIKE N'Added model' + N'%' AND (LEFT([m].[Name], LEN(N'Added model')) = N'Added model')
go
SELECT columnproperty(object_id('dbo.[Model]'),'Id','IsIdentity');
go
SELECT TOP 0 T.[Id] INTO dbo.[ModelTemp208f3efb] FROM dbo.[Model] AS T LEFT JOIN dbo.[Model] AS Source ON 1 = 0;
go
select @@trancount; SET FMTONLY ON select * from dbo.[ModelTemp208f3efb] SET FMTONLY OFF exec ..sp_tablecollations_100 N'[dbo].[ModelTemp208f3efb]'
go
insert bulk dbo.[ModelTemp208f3efb] ([Id] Int)
go
MERGE dbo.[Model] WITH (HOLDLOCK) AS T USING dbo.[ModelTemp208f3efb] AS S ON T.[Id] = S.[Id] WHEN MATCHED THEN DELETE;
go
DROP TABLE dbo.[ModelTemp208f3efb]
go

Remarque: un moyen plus efficace d'effectuer une suppression "en masse" serait de fournir un IQueryable qui spécifie la façon dont les éléments doivent être récupérés et génère un DELETE similaire au suivant:

DELETE FROM SomeTable
WHERE Id IN (SELECT Id FROM SomeTable WHERE ...)

Ceci est plus rapide car il ne nécessite pas de charger les entités EF, ni de créer une table temporaire et MERGE contre elle.

J'ai utilisé une bibliothèque pour Entity Framework 6, mais je n'ai pas pu trouver une bibliothèque non commerciale pour EF Core.

3
Alexei