Как удалить несколько строк в 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]");
}
TRUNCATE TABLE (Transact-SQL) удаляет все строки из таблицы без регистрации отдельных удалений строк. TRUNCATE TABLE аналогична инструкции DELETE без предложения WHERE; однако TRUNCATE TABLE работает быстрее и использует меньше ресурсов системы и журнала транзакций.
для этого можно использовать библиотеки расширений, например EntityFramework.Расширенный или Z. EntityFramework.Плюс.EF6, доступны для EF 5, 6 или Core. Эти библиотеки имеют большую производительность, когда вам нужно удалить или обновить, и они используют 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
- это очень элегантный и быстрый способ пакетного удаления, но его необходимо использовать с осторожностью:
- автоматическая генерация транзакции: ее запросы будут охватываться транзакцией
- независимость контекста базы данных: его выполнение не имеет ничего общего с
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;
}
лучше : in EF6 => .RemoveRange()
пример:
db.Table.RemoveRange(db.Table.Where(x => Field == "Something"));