Come faccio a eliminare più righe in Entity Framework (senza foreach)

Sto eliminando diversi elementi da una tabella usando Entity Framework. Non esiste una chiave esterna / object principale, quindi non posso gestirlo con OnDeleteCascade.

In questo momento sto facendo questo:

var widgets = context.Widgets .Where(w => w.WidgetId == widgetId); foreach (Widget widget in widgets) { context.Widgets.DeleteObject(widget); } context.SaveChanges(); 

Funziona ma il foreach mi infastidisce. Sto usando EF4 ma non voglio eseguire SQL. Voglio solo assicurarmi che non mi manchi niente – questo è buono come lo è, giusto? Posso estrapolarlo con un metodo di estensione o un aiuto, ma da qualche parte stiamo ancora facendo un foreach, giusto?

Se non si desidera eseguire SQL direttamente chiamando DeleteObject in un ciclo è quanto di meglio si possa fare oggi.

Tuttavia, è ansible eseguire SQL e renderlo ancora completamente generale tramite un metodo di estensione, utilizzando l’approccio che descrivo qui .

Sebbene quella risposta fosse per 3.5. Per la 4.0 probabilmente utilizzerei la nuova API ExecuteStoreCommand sotto il cofano, invece di scendere a StoreConnection.

EntityFramework 6 lo ha reso un po ‘più semplice con .RemoveRange() .

Esempio:

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

questo è buono come si arriva, giusto? Posso estrapolarlo con un metodo di estensione o un aiuto, ma da qualche parte stiamo ancora facendo un foreach, giusto?

Beh, sì, tranne che puoi farcela in un doppio rivestimento:

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

So che è abbastanza tardi, ma se qualcuno ha bisogno di una soluzione semplice, la cosa interessante è che puoi aggiungere anche la clausola where:

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

Nota: appena testato con MSSQL2008.

Aggiornamento: la soluzione sopra non funzionerà quando EF genera un’istruzione SQL con parametri , quindi ecco l’aggiornamento per EF5 :

  public static void DeleteWhere(this DbContext db, Expression> filter) where T : class { var query = db.Set().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); } 

Richiede un po ‘di riflessione ma funziona bene.

Per chiunque usi EF5, è ansible utilizzare la seguente libreria di estensioni: https://github.com/loresoft/EntityFramework.Extended

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

EF 6.1

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

Uso:

 // Delete where condition is met. DeleteWhere(d => d.Name == "Something"); Or: // delete all from entity DeleteWhere(); 

Sembra ancora folle dover recuperare qualcosa dal server solo per cancellarlo, ma almeno recuperare gli ID è molto più snello che abbattere le quadro complete:

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

Per EF 4.1,

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

Il modo più rapido per eliminare è utilizzare una stored procedure. Preferisco le stored procedure in un progetto di database su SQL dinamico perché i nomi dei nomi verranno gestiti correttamente e avranno errori del compilatore. SQL dinamico potrebbe fare riferimento a tabelle che sono state eliminate / rinominate causando errori di runtime.

In questo esempio, ho due tabelle List e ListItems. Ho bisogno di un modo rapido per cancellare tutti i ListItem di una data lista.

 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 

Ora la parte interessante dell’eliminazione degli articoli e dell’aggiornamento del framework Entity utilizzando un’estensione.

 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; } } } 

Il codice principale ora può usarlo come

 [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); } } 

Se si desidera eliminare tutte le righe di una tabella, è ansible eseguire il comando sql

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

TRUNCATE TABLE (Transact-SQL) Rimuove tutte le righe da una tabella senza registrare le singole eliminazioni di riga. TRUNCATE TABLE è simile all’istruzione DELETE con nessuna clausola WHERE; tuttavia, TRUNCATE TABLE è più veloce e utilizza meno risorse di registro di sistema e delle transazioni.

È ansible utilizzare le librerie di estensioni per fare ciò come EntityFramework.Extended o Z.EntityFramework.Plus.EF6, sono disponibili per EF 5, 6 o Core. Queste librerie hanno grandi prestazioni quando devi eliminare o aggiornare e usano LINQ. Esempio per l’eliminazione ( fonte più ):

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

o ( fonte estesa )

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

Questi usano istruzioni SQL native, quindi le prestazioni sono grandiose.

È ansible eseguire query SQL direttamente come segue:

  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; } 

Per selezionare possiamo usare

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

È anche ansible utilizzare il metodo DeleteAllOnSubmit () passando i risultati in un elenco generico anziché in var. In questo modo il tuo foreach si riduce a una riga di codice:

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

Probabilmente utilizza comunque un ciclo internamente.

UUHHIVS è un modo molto elegante e veloce per eliminare i lotti, ma deve essere usato con cura:

  • generazione automatica della transazione: le sue query saranno incluse in una transazione
  • indipendenza dal contesto del database: la sua esecuzione non ha nulla a che fare con context.SaveChanges()

Questi problemi possono essere aggirati assumendo il controllo della transazione. Il seguente codice illustra come eliminare i batch e inserire bulk in maniera transazionale:

 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 performsd // 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(); } 

EF 6. =>

 var assignmentAddedContent = dbHazirBot.tbl_AssignmentAddedContent.Where(a => a.HazirBot_CategoryAssignmentID == categoryAssignment.HazirBot_CategoryAssignmentID); dbHazirBot.tbl_AssignmentAddedContent.RemoveRange(assignmentAddedContent); dbHazirBot.SaveChanges(); 

Vedi la risposta “bit di codice preferito” che funziona

Ecco come l’ho usato:

  // Delete all rows from the WebLog table via the EF database context object // using a where clause that returns an IEnumerable typed list WebLog class public IEnumerable DeleteAllWebLogEntries() { IEnumerable myEntities = context.WebLog.Where(e => e.WebLog_ID > 0); context.WebLog.RemoveRange(myEntities); context.SaveChanges(); return myEntities; } 

Migliore: in EF6 => .RemoveRange()

Esempio:

 db.Table.RemoveRange(db.Table.Where(x => Field == "Something")); 
  int id = 5; db.tablename.RemoveRange(db.tablename.Where(c => c.firstid == id));