web-dev-qa-db-fra.com

Comment supprimer plusieurs lignes dans Entity Framework (sans foreach)

Je supprime plusieurs éléments d'une table à l'aide d'Entity Framework. Il n'y a pas de clé étrangère/objet parent, je ne peux donc pas gérer cela avec OnDeleteCascade.

En ce moment je fais ça:

var widgets = context.Widgets
    .Where(w => w.WidgetId == widgetId);

foreach (Widget widget in widgets)
{
    context.Widgets.DeleteObject(widget);
}
context.SaveChanges();

Cela fonctionne mais le foreach me dérange. J'utilise EF4 mais je ne veux pas exécuter SQL. Je veux juste m'assurer que je ne manque de rien - c'est aussi bon que possible, non? Je peux le résumer avec une méthode d'extension ou une aide, mais quelque part, nous allons toujours faire un foreach, n'est-ce pas?

268
Jon Galloway

Si vous ne voulez pas exécuter SQL, appeler directement DeleteObject dans une boucle est ce que vous pouvez faire de mieux aujourd'hui. 

Cependant, vous pouvez exécuter SQL et le rendre tout à fait général via une méthode d'extension, en utilisant l'approche que je décris ici .

Bien que cette réponse était pour 3.5. Pour la version 4.0, j'utiliserais probablement la nouvelle API ExecuteStoreCommand sous le capot, au lieu de descendre à StoreConnection.

47
Alex James

EntityFramework 6 a un peu simplifié cela avec .RemoveRange().

Exemple:

db.People.RemoveRange(db.People.Where(x => x.State == "CA"));
db.SaveChanges();
583
Kyle

c'est aussi bon que ça, non? Je peux le résumer avec une extension méthode ou assistant, mais quelque part nous allons toujours faire un foreach, c'est ça?

Eh bien, oui, sauf que vous pouvez en faire une double ligne:

context.Widgets.Where(w => w.WidgetId == widgetId)
               .ToList().ForEach(context.Widgets.DeleteObject);
context.SaveChanges();
74
using (var context = new DatabaseEntities())
{
    context.ExecuteStoreCommand("DELETE FROM YOURTABLE WHERE CustomerID = {0}", customerId);
}
68
Vlad Bezden

Je sais que c'est assez tard, mais au cas où quelqu'un aurait besoin d'une solution simple, le plus intéressant, c'est que vous pouvez aussi ajouter la clause where avec:

public static void DeleteWhere<T>(this DbContext db, Expression<Func<T, bool>> filter) where T : class
{
    string selectSql = db.Set<T>().Where(filter).ToString();
    string fromWhere = selectSql.Substring(selectSql.IndexOf("FROM"));
    string deleteSql = "DELETE [Extent1] " + fromWhere;
    db.Database.ExecuteSqlCommand(deleteSql);
}

Remarque: vient d'être testé avec MSSQL2008.

Mettre à jour:

La solution ci-dessus ne fonctionnera pas si EF génère une instruction SQL avec parameters , voici donc la mise à jour pour EF5 :

public static void DeleteWhere<T>(this DbContext db, Expression<Func<T, bool>> filter) where T : class
{
    var query = db.Set<T>().Where(filter);

    string selectSql = query.ToString();
    string deleteSql = "DELETE [Extent1] " + selectSql.Substring(selectSql.IndexOf("FROM"));

    var internalQuery = query.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(field => field.Name == "_internalQuery").Select(field => field.GetValue(query)).First();
    var objectQuery = internalQuery.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(field => field.Name == "_objectQuery").Select(field => field.GetValue(internalQuery)).First() as ObjectQuery;
    var parameters = objectQuery.Parameters.Select(p => new SqlParameter(p.Name, p.Value)).ToArray();

    db.Database.ExecuteSqlCommand(deleteSql, parameters);
}

Cela demande un peu de réflexion mais fonctionne bien.

41
Thanh Nguyen

Pour toute personne utilisant EF5, la bibliothèque d’extensions suivante peut être utilisée: https://github.com/loresoft/EntityFramework.Extended

context.Widgets.Delete(w => w.WidgetId == widgetId);
30
Marcelo Mason

Cela semble toujours fou de devoir retirer quelque chose du serveur juste pour le supprimer, mais au moins, récupérer juste les identifiants est beaucoup plus maigre que de supprimer toutes les entités:

var ids = from w in context.Widgets where w.WidgetId == widgetId select w.Id;
context.Widgets.RemoveRange(from id in ids.AsEnumerable() select new Widget { Id = id });
10
Edward Brey

EF 6.1

public void DeleteWhere<TEntity>(Expression<Func<TEntity, bool>> predicate = null) 
where TEntity : class
{
    var dbSet = context.Set<TEntity>();
    if (predicate != null)
        dbSet.RemoveRange(dbSet.Where(predicate));
    else
        dbSet.RemoveRange(dbSet);

    context.SaveChanges();
} 

Usage:

// Delete where condition is met.
DeleteWhere<MyEntity>(d => d.Name == "Something");

Or:

// delete all from entity
DeleteWhere<MyEntity>();
10
jzm

Pour EF 4.1, 

var objectContext = (myEntities as IObjectContextAdapter).ObjectContext;
objectContext.ExecuteStoreCommand("delete from [myTable];");
4
Amit Pawar

Si vous souhaitez supprimer toutes les lignes d'une table, vous pouvez exécuter la commande SQL

using (var context = new DataDb())
{
     context.Database.ExecuteSqlCommand("TRUNCATE TABLE [TableName]");
}

TRUNCATE TABLE (Transact-SQL) Supprime toutes les lignes d'une table sans consigner les suppressions de lignes individuelles. TRUNCATE TABLE est similaire à l'instruction DELETE sans clause WHERE; Cependant, TRUNCATE TABLE est plus rapide et utilise moins de ressources système et de journal de transactions.

3
mirtiger

Vous pouvez utiliser des bibliothèques d’extensions comme EntityFramework.Extended ou Z.EntityFramework.Plus.EF6, disponibles pour EF 5, 6 ou Core. Ces bibliothèques offrent d'excellentes performances lorsque vous devez supprimer ou mettre à jour et utilisent LINQ. Exemple de suppression ( source plus ):

ctx.Users.Where(x => x.LastLoginDate < DateTime.Now.AddYears(-2)) .Delete();

ou ( source étendue )

context.Users.Where(u => u.FirstName == "firstname") .Delete();

Ceux-ci utilisent des instructions SQL natives, donc les performances sont excellentes.

3
UUHHIVS

Le moyen le plus rapide de supprimer consiste à utiliser une procédure stockée. Je préfère les procédures stockées dans un projet de base de données plutôt que le SQL dynamique, car les renommage seront gérés correctement et comportent des erreurs de compilation. Le SQL dynamique peut faire référence à des tables supprimées/renommées, générant des erreurs d'exécution.

Dans cet exemple, j'ai deux tables List et ListItems. J'ai besoin d'un moyen rapide pour supprimer tous les ListItems d'une liste donnée.

CREATE TABLE [act].[Lists]
(
    [Id] INT NOT NULL PRIMARY KEY IDENTITY, 
    [Name] NVARCHAR(50) NOT NULL
)
GO
CREATE UNIQUE INDEX [IU_Name] ON [act].[Lists] ([Name])
GO
CREATE TABLE [act].[ListItems]
(
    [Id] INT NOT NULL IDENTITY, 
    [ListId] INT NOT NULL, 
    [Item] NVARCHAR(100) NOT NULL, 
    CONSTRAINT PK_ListItems_Id PRIMARY KEY NONCLUSTERED (Id),
    CONSTRAINT [FK_ListItems_Lists] FOREIGN KEY ([ListId]) REFERENCES [act].[Lists]([Id]) ON DELETE CASCADE
)
go
CREATE UNIQUE CLUSTERED INDEX IX_ListItems_Item 
ON [act].[ListItems] ([ListId], [Item]); 
GO

CREATE PROCEDURE [act].[DeleteAllItemsInList]
    @listId int
AS
    DELETE FROM act.ListItems where ListId = @listId
RETURN 0

Maintenant, la partie intéressante de la suppression des éléments et de la mise à jour de la structure Entity à l’aide d’une extension.

public static class ListExtension
{
    public static void DeleteAllListItems(this List list, ActDbContext db)
    {
        if (list.Id > 0)
        {
            var listIdParameter = new SqlParameter("ListId", list.Id);
            db.Database.ExecuteSqlCommand("[act].[DeleteAllItemsInList] @ListId", listIdParameter);
        }
        foreach (var listItem in list.ListItems.ToList())
        {
            db.Entry(listItem).State = EntityState.Detached;
        }
    }
}

Le code principal peut maintenant être utilisé tel quel

[TestMethod]
public void DeleteAllItemsInListAfterSavingToDatabase()
{
    using (var db = new ActDbContext())
    {
        var listName = "TestList";
        // Clean up
        var listInDb = db.Lists.Where(r => r.Name == listName).FirstOrDefault();
        if (listInDb != null)
        {
            db.Lists.Remove(listInDb);
            db.SaveChanges();
        }

        // Test
        var list = new List() { Name = listName };
        list.ListItems.Add(new ListItem() { Item = "Item 1" });
        list.ListItems.Add(new ListItem() { Item = "Item 2" });
        db.Lists.Add(list);
        db.SaveChanges();
        listInDb = db.Lists.Find(list.Id);
        Assert.AreEqual(2, list.ListItems.Count);
        list.DeleteAllListItems(db);
        db.SaveChanges();
        listInDb = db.Lists.Find(list.Id);
        Assert.AreEqual(0, list.ListItems.Count);
    }
}
3
Xavier John

UUHHIVS 's est un moyen très élégant et rapide pour supprimer des lots, mais il doit être utilisé avec précaution:

  • génération automatique de transaction: ses requêtes seront englobées par une transaction
  • indépendance du contexte de la base de données: son exécution n'a rien à voir avec context.SaveChanges()

Ces problèmes peuvent être contournés en prenant le contrôle de la transaction. Le code suivant montre comment supprimer et insérer en bloc de manière transactionnelle:

var repo = DataAccess.EntityRepository;
var existingData = repo.All.Where(x => x.ParentId == parentId);  

TransactionScope scope = null;
try
{
    // this starts the outer transaction 
    using (scope = new TransactionScope(TransactionScopeOption.Required))
    {
        // this starts and commits an inner transaction
        existingData.Delete();

        // var toInsert = ... 

        // this relies on EntityFramework.BulkInsert library
        repo.BulkInsert(toInsert);

        // any other context changes can be performed

        // this starts and commit an inner transaction
        DataAccess.SaveChanges();

        // this commit the outer transaction
        scope.Complete();
    }
}
catch (Exception exc)
{
    // this also rollbacks any pending transactions
    scope?.Dispose();
}
2
Alexei

Vous pouvez également utiliser la méthode DeleteAllOnSubmit () en lui transmettant vos résultats dans une liste generic plutôt que dans var. De cette façon, votre foreach se réduit à une ligne de code:

List<Widgets> widgetList = context.Widgets
              .Where(w => w.WidgetId == widgetId).ToList<Widgets>();

context.Widgets.DeleteAllOnSubmit(widgetList);

context.SubmitChanges();

Il utilise probablement encore une boucle en interne.

1
Hugo Nava Kopp

Vous pouvez exécuter des requêtes SQL directement comme suit:

    private int DeleteData()
{
    using (var ctx = new MyEntities(this.ConnectionString))
    {
        if (ctx != null)
        {

            //Delete command
            return ctx.ExecuteStoreCommand("DELETE FROM ALARM WHERE AlarmID > 100");

        }
    }
    return 0;
}

Pour sélectionner, nous pouvons utiliser

using (var context = new MyContext()) 
{ 
    var blogs = context.MyTable.SqlQuery("SELECT * FROM dbo.MyTable").ToList(); 
}
1
Abhishek Sharma

Dans EF 6.2, cela fonctionne parfaitement, envoyer la suppression directement à la base de données sans charger au préalable les entités:

context.Widgets.Where(predicate).Delete();

Avec un prédicat fixe, c'est assez simple:

context.Widgets.Where(w => w.WidgetId == widgetId).Delete();

Et si vous avez besoin d'un prédicat dynamique, consultez LINQKit (paquet Nuget disponible), une fonction comme celle-ci fonctionne bien dans mon cas:

Expression<Func<Widget, bool>> predicate = PredicateBuilder.New<Widget>(x => x.UserID == userID);
if (somePropertyValue != null)
{
    predicate = predicate.And(w => w.SomeProperty == somePropertyValue);
}
context.Widgets.Where(predicate).Delete();
0
Vladimir

EF 6. =>

var assignmentAddedContent = dbHazirBot.tbl_AssignmentAddedContent.Where(a =>
a.HazirBot_CategoryAssignmentID == categoryAssignment.HazirBot_CategoryAssignmentID);
dbHazirBot.tbl_AssignmentAddedContent.RemoveRange(assignmentAddedContent);
dbHazirBot.SaveChanges();
0
Erçin Dedeoğlu