Как удалить несколько строк в 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. Я просто хочу удостовериться, что я ничего не пропустил - это так хорошо, как получается, не так ли? Я могу абстрагировать его с помощью метода расширения или помощника, но где-то мы все еще будем делать foreach, верно?
Ответы
Ответ 1
Если вы не хотите выполнять SQL, напрямую вызывающий DeleteObject в цикле, это лучшее, что вы можете сделать сегодня.
Однако вы можете выполнить SQL и по-прежнему полностью использовать его с помощью метода расширения, используя подход, описывающий здесь.
Хотя этот ответ был для 3.5. Для 4.0 я, вероятно, использовал бы новый API ExecuteStoreCommand под капотом, вместо того, чтобы сбрасываться в StoreConnection.
Ответ 2
EntityFramework 6 упростил это с помощью .RemoveRange()
.
Пример:
db.People.RemoveRange(db.People.Where(x => x.State == "CA"));
db.SaveChanges();
Ответ 3
Это так же хорошо, как и получается, не так ли? Я могу абстрагировать его с расширением метод или помощник, но где-то мы все еще будем делать foreach, правильно?
Хорошо, да, кроме того, что вы можете сделать это в двухстрочный:
context.Widgets.Where(w => w.WidgetId == widgetId)
.ToList().ForEach(context.Widgets.DeleteObject);
context.SaveChanges();
Ответ 4
using (var context = new DatabaseEntities())
{
context.ExecuteStoreCommand("DELETE FROM YOURTABLE WHERE CustomerID = {0}", customerId);
}
Ответ 5
Я знаю это довольно поздно, но в случае, если кому-то нужно простое решение, здорово, вы также можете добавить предложение 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.
Update:
Вышеприведенное решение не будет работать, когда 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);
}
Он требует немного отражения, но работает хорошо.
Ответ 6
Для тех, кто использует EF5, можно использовать следующую библиотеку расширений: https://github.com/loresoft/EntityFramework.Extended
context.Widgets.Delete(w => w.WidgetId == widgetId);
Ответ 7
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>();
Ответ 8
Все еще кажется сумасшедшим, что нужно вытаскивать что-либо с сервера только для его удаления, но, по крайней мере, возвращение только идентификаторов намного более компактное, чем удаление всех объектов:
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 });
Ответ 9
Для EF 4.1,
var objectContext = (myEntities as IObjectContextAdapter).ObjectContext;
objectContext.ExecuteStoreCommand("delete from [myTable];");
Ответ 10
Самый быстрый способ удаления - использовать хранимую процедуру. Я предпочитаю хранимые процедуры в проекте базы данных над динамическим SQL, потому что переименования будут обрабатываться правильно и иметь ошибки компилятора. Динамический SQL может ссылаться на таблицы, которые были удалены/переименованы, вызывая ошибки времени выполнения.
В этом примере у меня есть две таблицы List и 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 с помощью расширения.
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);
}
}
Ответ 11
Если вы хотите удалить все строки таблицы, вы можете выполнить команду sql
using (var context = new DataDb())
{
context.Database.ExecuteSqlCommand("TRUNCATE TABLE [TableName]");
}
TRUNCATE TABLE (Transact-SQL) Удаляет все строки из таблицы без регистрации отдельных удалений строк. TRUNCATE TABLE похож на оператор DELETE без предложения WHERE; однако TRUNCATE TABLE работает быстрее и использует меньше ресурсов системного и транзакционного журнала.
Ответ 12
Вы можете использовать библиотеки расширений для выполнения таких действий, как EntityFramework.Extended или Z.EntityFramework.Plus.EF6, для EF 5, 6 или Core доступны. Эти библиотеки обладают большой производительностью, когда вам нужно удалить или обновить, и они используют LINQ. Пример для удаления (source plus):
ctx.Users.Where(x => x.LastLoginDate < DateTime.Now.AddYears(-2))
.Delete();
или (расширенный источник)
context.Users.Where(u => u.FirstName == "firstname")
.Delete();
Они используют собственные инструкции SQL, поэтому производительность отличная.
Ответ 13
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();
}
Ответ 14
Вы можете выполнить 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();
}
Ответ 15
Вы также можете использовать метод DeleteAllOnSubmit(), передав его результаты в общий список, а не в var. Таким образом, ваш foreach сводится к одной строке кода:
List<Widgets> widgetList = context.Widgets
.Where(w => w.WidgetId == widgetId).ToList<Widgets>();
context.Widgets.DeleteAllOnSubmit(widgetList);
context.SubmitChanges();
Он, вероятно, все еще использует цикл внутри, хотя.
Ответ 16
EF 6. = >
var assignmentAddedContent = dbHazirBot.tbl_AssignmentAddedContent.Where(a =>
a.HazirBot_CategoryAssignmentID == categoryAssignment.HazirBot_CategoryAssignmentID);
dbHazirBot.tbl_AssignmentAddedContent.RemoveRange(assignmentAddedContent);
dbHazirBot.SaveChanges();
Ответ 17
В EF 6.2 это работает отлично, отправляя удаление непосредственно в базу данных без предварительной загрузки сущностей:
context.Widgets.Where(predicate).Delete();
С фиксированным предикатом это довольно просто:
context.Widgets.Where(w => w.WidgetId == widgetId).Delete();
И если вам нужен динамический предикат, взгляните на LINQKit (доступен пакет Nuget), что-то вроде этого отлично работает в моем случае:
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();
Ответ 18
См. ответ "любимый бит кода", который работает
Вот как я его использовал:
// 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;
}
Ответ 19
Лучшее: in EF6 => .RemoveRange()
Пример:
db.Table.RemoveRange(db.Table.Where(x => Field == "Something"));
Ответ 20
int id = 5;
db.tablename.RemoveRange(db.tablename.Where(c => c.firstid == id));