我如何删除实体框架中的多行(没有foreach)

我正在使用实体框架从表中删除几个项目。没有外键/父对象,所以我不能用OnDeleteCascade处理这个。

现在我正在做这个:

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


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

它工作,但foreach困扰我。我使用EF4,但我不想执行SQL。我只是想确保我没有错过任何东西-这是最好的,对吧?我可以用扩展方法或helper来抽象它,但在某些地方我们还是要用foreach,对吧?

393455 次浏览

这就是最好的结果了,对吧?我可以用扩展来抽象它 方法或助手,但我们还是会用到 foreach ?< / p >

是的,不过你可以把它写成两行字:

context.Widgets.Where(w => w.WidgetId == widgetId)
.ToList().ForEach(context.Widgets.DeleteObject);
context.SaveChanges();

如果你不想直接执行SQL,在循环中调用DeleteObject是最好的方法。

然而,你可以执行SQL,并通过扩展方法使其完全通用,使用我描述的方法在这里

尽管答案是3.5。对于4.0,我可能会在底层使用新的ExecuteStoreCommand API,而不是下拉到StoreConnection。

using (var context = new DatabaseEntities())
{
context.ExecuteStoreCommand("DELETE FROM YOURTABLE WHERE CustomerID = {0}", customerId);
}

新增:支持可编写的id列表

var listOfIds = String.Join(',',customerIds.Select(id => $"'{id}'").ToList());
var sql= $@"DELETE  [YOURTABLE] WHERE CustomerID in ({listOfIds})";

注意:如果CustomerID是一个字符串,您应该仔细检查潜在的SQL注入风险,对于整数CustomerID是安全的

对于EF 4.1,

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

我知道已经很晚了,但如果有人需要一个简单的解决方案,最酷的事情是你还可以添加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);

EntityFramework 6使用.RemoveRange()使这更容易一些。

例子:

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

警告!不要在大型数据集上使用此方法!

EF将所有数据拉入内存,然后删除它。对于较小的数据集,这可能不是一个问题,但通常避免这种类型的删除,除非你能保证你只做非常小的更改。

您可以很容易地将进程运行到内存不足的状态,而EF会很高兴地提取您指定的所有数据来删除它。

请看答案“最喜欢的代码”

下面是我如何使用它:

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

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

可以通过以下方式直接执行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();

它可能仍然在内部使用循环。

EF 6。= >

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

最快的删除方法是使用存储过程。与动态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;
}
}
}

现在可以使用的主要代码是as

[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类似于没有WHERE子句的DELETE语句;然而,TRUNCATE TABLE更快,使用更少的系统和事务日志资源。

最佳:in EF6 => .RemoveRange()

例子:

db.Table.RemoveRange(db.Table.Where(x => Field == "Something"));

你可以使用像EntityFramework这样的扩展库。Extended或Z.EntityFramework.Plus。EF6,有EF 5, 6或核心可用。当你必须删除或更新时,这些库具有出色的性能,并且它们使用LINQ。删除(源+)的示例:

< p > <代码> ctx.Users。在(x =比;x.LastLoginDate & lt;DateTime.Now.AddYears (2)) delete(); < /代码> < / p >

或(源扩展)

< p > <代码> context.Users。在哪里(u =比;u.FirstName == "firstname") delete(); < /代码> < / p >

它们使用本地SQL语句,因此性能非常好。

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

从服务器上拉回任何东西来删除它看起来仍然很疯狂,但至少只返回id比拉下完整的实体要精简得多:

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

实体框架核心

3.1 3.0 2.2 2.1 2.0 1.1 1.0

using (YourContext context = new YourContext ())
{
var widgets = context.Widgets.Where(w => w.WidgetId == widgetId);
context.Widgets.RemoveRange(widgets);
context.SaveChanges();
}

总结:

从集合底层的上下文中移除给定的实体集合 每个实体都处于已删除状态,这样它就会被删除 当SaveChanges被调用时,从数据库中删除

讲话:

如果System.Data.Entity.Infrastructure.DbContextConfiguration.AutoDetectChangesEnabled < p >注意 设置为true(这是默认值),那么DetectChanges将被调用一次 之前删除任何实体,将不会再次调用。这意味着 RemoveRange可能比调用Remove multiple执行得更好 时间就够了。注意,如果上下文中存在任何处于已添加状态的实体, 然后,该方法将导致它与上下文分离。这是因为 被添加的实体被假定在数据库中不存在,因此试图删除

Thanh的回答最适合我。在一次服务器访问中删除了我所有的记录。我挣扎着实际调用扩展方法,所以我想分享我的(EF 6):

我将扩展方法添加到我的MVC项目中的一个助手类中,并将名称更改为“RemoveWhere”。我在控制器中注入了一个dbContext,但你也可以做一个using

// make a list of items to delete or just use conditionals against fields
var idsToFilter = dbContext.Products
.Where(p => p.IsExpired)
.Select(p => p.ProductId)
.ToList();


// build the expression
Expression<Func<Product, bool>> deleteList =
(a) => idsToFilter.Contains(a.ProductId);


// Run the extension method (make sure you have `using namespace` at the top)
dbContext.RemoveWhere(deleteList);

这将为组生成一条delete语句。

 context.Widgets.RemoveRange(context.Widgets.Where(w => w.WidgetId == widgetId).ToList());
db.SaveChanges();

如果您正在使用通用存储库:

在泛型存储库中,下面可能是新方法。

       public void RemoveMultiple(Expression<Func<T, bool>> predicate)
{
IQueryable<T> query = _context.Set<T>().Where(predicate);
_context.Set<T>().RemoveRange(query.AsNoTracking());
            

}

用法:

_unitOfWork.YOUR_ENTITY.RemoveMultiple(x => x.AccountId == accountId);
_unitOfWork.Complete();

我想出了一个很棒的库Zack.EFCore.Batch。它将你的表达式转换为简单的DELETE FROM .... WHERE查询。(就像一些建议的答案)https://github.com/yangzhongke/Zack.EFCore.Batch

使用示例:

await ctx.DeleteRangeAsync<Book>(b => b.Price > n);

Zack.EFCore.Batch库比Z.EntityFramework.Extended https://entityframework-extensions.net/有很多好处,后者没有真正的Async方法。(它们只是同步方法的包装)在高负载环境中使用这个库可能会遇到很多意想不到的问题。

最后,这已经在实体框架核心7中通过ExecuteDelete命令引入:

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

我知道问题是EF4,但如果升级可以是一个选择!

在EF 7中,您可以使用批量删除

    var ids = widgets.Select(x => x.Id).ToList();
await _mrVodDbContext.Widgets.Where(x => ids.Contains(x.Id)).ExecuteDeleteAsync();

EF磁芯发生器

  DELETE FROM [i]
FROM [Widgets] AS [i]
WHERE [i].[Id] IN (4,3,2,1)

更多关于在发行说明中删除或更新的信息。https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/whatsnew#basic-executedelete-examples