Как удалить несколько строк в Entity Framework (без foreach)
Я удаляю несколько элементов из таблицы с помощью Entity Framework. Нет внешнего ключа / родительского объекта, поэтому я не могу справиться с этим с помощью OnDeleteCascade.
сейчас я делаю так:
var widgets = context.Widgets
.Where(w => w.WidgetId == widgetId);
foreach (Widget widget in widgets)
{
context.Widgets.DeleteObject(widget);
}
context.SaveChanges();
это работает, но foreach меня достает. Я использую EF4, но я не хочу выполнять SQL. Я просто хочу убедиться, что ничего не упустил - это так же хорошо, как и получается, верно? Я могу абстрагировать его с помощью метода расширения или помощника, но где-то мы все еще будем делать форч, верно?
19 ответов:
Если вы не хотите выполнять SQL напрямую, вызов DeleteObject в цикле-это лучшее, что вы можете сделать сегодня.
однако вы можете выполнить SQL и по-прежнему сделать его полностью универсальным с помощью метода расширения, используя подход, который я описываю здесь.
хотя этот ответ был для 3.5. Для 4.0 я бы, вероятно, использовал новый API ExecuteStoreCommand под капотом, вместо того, чтобы опускаться до StoreConnection.
EntityFramework 6 сделал это немного проще с
.RemoveRange().пример:
db.People.RemoveRange(db.People.Where(x => x.State == "CA")); db.SaveChanges();
это так хорошо, как он получает, да? Я могу абстрагировать его с расширением способ или помощник, но где-то мы все равно будем делать для начала, верно?
Ну да, за исключением того, что вы можете сделать это в два лайнера:
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); }
Я знаю, что это довольно поздно, но если кому-то нужно простое решение, отличная вещь, можно добавить предложение where это:
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); }Примечание: только что протестировано с MSSQL2008.
обновление: Решение выше не будет работать, когда EF генерирует инструкцию sql с параметры, так вот обновление для 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); }это требует немного размышлений, но работает хорошо.
для всех, кто использует EF5, можно использовать следующую библиотеку расширений:https://github.com/loresoft/EntityFramework.Extended
context.Widgets.Delete(w => w.WidgetId == widgetId);
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(); }использование:
// Delete where condition is met. DeleteWhere<MyEntity>(d => d.Name == "Something"); Or: // delete all from entity DeleteWhere<MyEntity>();
все еще кажется сумасшедшим, чтобы вытащить что-нибудь с сервера, чтобы просто удалить его, но, по крайней мере, возвращение только идентификаторов намного меньше, чем удаление полных сущностей:
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 });
для EF 4.1,
var objectContext = (myEntities as IObjectContextAdapter).ObjectContext; objectContext.ExecuteStoreCommand("delete from [myTable];");
самый быстрый способ удаления-это использование хранимой процедуры. Я предпочитаю хранимые процедуры в базе данных по динамическим SQL, потому что переименование будет правильно и ошибок компиляции. Динамический SQL может ссылаться на таблицы, которые были удалены/переименованы, вызывая ошибки времени выполнения.
в этом примере у меня есть два списка таблиц и ListItems. Мне нужен быстрый способ удалить все ListItems данного списка.
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Теперь интересная часть удаления элементов и обновление Entity framework с помощью расширения.
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; } } }основной код теперь может использовать это как
[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); } }
Если вы хотите удалить все строки таблицы, вы можете выполнить команду sql
using (var context = new DataDb()) { context.Database.ExecuteSqlCommand("TRUNCATE TABLE [TableName]"); }усечение таблицы (Transact-SQL) удаляет все строки из таблицы без регистрации отдельных удалений строк. Усечение таблицы аналогично инструкции DELETE без предложения WHERE; однако усечение таблицы выполняется быстрее и использует меньше системных ресурсов и ресурсов журнала транзакций.
вы можете использовать библиотеки расширений для этого, как EntityFramework.Расширенный или Z. EntityFramework.Плюс.ЭФ6, там доступны для эф 5, 6 или ядра. Эти библиотеки имеют большую производительность, когда вам нужно удалить или обновить, и они используют LINQ. Пример для удаления (Источник Плюс):
ctx.Users.Where(x => x.LastLoginDate < DateTime.Now.AddYears(-2)) .Delete();или (источник продлен)
context.Users.Where(u => u.FirstName == "firstname") .Delete();Они используют собственные операторы SQL, поэтому производительность велика.
Вы можете выполнять SQL-запросы напрямую следующим образом :
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; }для выбора мы можем использовать
using (var context = new MyContext()) { var blogs = context.MyTable.SqlQuery("SELECT * FROM dbo.MyTable").ToList(); }
вы также можете использовать DeleteAllOnSubmit() метод, передавая его ваши результаты в общий список а не в var. Таким образом, ваш foreach сводится к одной строке кода:
List<Widgets> widgetList = context.Widgets .Where(w => w.WidgetId == widgetId).ToList<Widgets>(); context.Widgets.DeleteAllOnSubmit(widgetList); context.SubmitChanges();он, вероятно, все еще использует цикл внутри, хотя.
UUHHIVS' s-это очень элегантный и быстрый способ для пакетного удаления, но он должен использоваться с осторожностью:
- автоматическая генерация транзакции: ее запросы будут охватываться транзакцией
- независимость контекста базы данных: ее выполнение не имеет ничего общего с
context.SaveChanges()эти проблемы можно обойти, взяв под контроль сделки. В следующем коде показано, как пакетное удаление и массовая вставка в транзакцию образом:
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(); }
EF 6.= >
var assignmentAddedContent = dbHazirBot.tbl_AssignmentAddedContent.Where(a => a.HazirBot_CategoryAssignmentID == categoryAssignment.HazirBot_CategoryAssignmentID); dbHazirBot.tbl_AssignmentAddedContent.RemoveRange(assignmentAddedContent); dbHazirBot.SaveChanges();
смотрите ответ "любимый бит кода", который работает
вот как я его использовал:
// 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<WebLog> DeleteAllWebLogEntries() { IEnumerable<WebLog> myEntities = context.WebLog.Where(e => e.WebLog_ID > 0); context.WebLog.RemoveRange(myEntities); context.SaveChanges(); return myEntities; }
Best:
in EF6 => .RemoveRange()пример:
db.Table.RemoveRange(db.Table.Where(x => Field == "Something"));
Comments