如何在Entity Framework中删除多行(没有foreach)

Jon*_*way 292 entity-framework

我正在使用Entity Framework从表中删除多个项目.没有外键/父对象,因此我无法使用OnDeleteCascade处理此问题.

现在我这样做:

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

foreach (Widget widget in widgets)
{
    context.Widgets.DeleteObject(widget);
}
context.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

它有效但是foreach让我烦恼.我正在使用EF4,但我不想执行SQL.我只是想确保我没有遗漏任何东西 - 这一切都很好,对吧?我可以用扩展方法或帮助器来抽象它,但在某个地方我们仍然会做一个foreach,对吧?

Kyl*_*yle 628

EntityFramework 6使这更容易了.RemoveRange().

例:

db.People.RemoveRange(db.People.Where(x => x.State == "CA"));
db.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

  • 当然,这个答案更容易,但性能方面可能不是很好.为什么?这个exatly doet与在foreach循环中删除它相同,它首先获取所有行然后逐个删除,只有获取用于保存"在删除任何实体之前将调用一次DetectChanges并且不再被调用"休息是一样的,尝试使用工具来查看生成的sql. (51认同)
  • 这正是我们所需要的......除非我在足够大的范围内使用它,否则会出现内存异常!我认为RemoveRange的重点是将处理传递给数据库,但显然不是. (31认同)
  • 问题是RemoveRange输入参数是IEnumerable,因此要执行delete,它枚举所有实体并对每个实体运行1 DELETE查询. (18认同)
  • 这似乎是一个非常不有效的方法。我在 SQL Profiler 中检查过:RemoveRange 命令实际上执行 SELECTcommand,而 SaveChanges 对第一个命令后找到的每条记录分别执行 DELETE 命令。在我看来,最好的方法是编写相关的存储过程并从 EF 执行它。 (6认同)
  • 对于足够大的范围,尝试类似.Take(10000)并循环直到RemoveRange(...).Count()== 0. (4认同)

Kla*_*sen 73

这是好事,对吧?我可以用扩展方法或帮助器来抽象它,但在某个地方我们仍然会做一个foreach,对吧?

嗯,是的,除了你可以把它变成两个班轮:

context.Widgets.Where(w => w.WidgetId == widgetId)
               .ToList().ForEach(context.Widgets.DeleteObject);
context.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

  • 你正在做一个违背目的的ToList().与原始解决方案有何不同? (74认同)
  • 我有问题因为我在上下文对象中只有Remove方法. (3认同)
  • 当预期有一百万行(甚至几百行)时,这绝对不是一个合适的解决方案.但是,如果我们确定只有几行,那么这个解决方案很简洁,效果非常好.是的,这将涉及到DB的几次往返,但在我看来,直接调用SQL所涉及的丢失抽象超过了好处. (2认同)

Vla*_*den 69

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

  • @ JesseNewman19如果你已经有了一个ID列表,请使用`WHERE IN({0})`,然后第二个参数应该是`String.Join(",",idList)`. (10认同)

Ale*_*mes 48

如果你不想直接在循环中调用DeleteObject来执行SQL,那么今天你可以做到最好.

但是,您可以使用我在此处描述的方法,通过扩展方法执行SQL并使其完全通用.

虽然答案是3.5.对于4.0,我可能会使用新的ExecuteStoreCommand API,而不是下载到StoreConnection.


小智 45

我知道它已经很晚了但是如果有人需要一个简单的解决方案,那么很酷的是你也可以用它添加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);
}
Run Code Online (Sandbox Code Playgroud)

注意:刚用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);
}
Run Code Online (Sandbox Code Playgroud)

它需要一点点反射,但效果很好.

  • 这是实际回答问题的唯一答案!每个其他答案一次删除一个项目,令人难以置信. (3认同)
  • 对于所有技术水平较低的程序员,我想详细说明如何实现这个优秀且通用的解决方案,因为它可以节省我几分钟的时间!在下一条评论中继续... (2认同)

ihe*_*heb 35

最后,在Entity Framework Core 7中通过以下命令引入了批量删除ExecuteDelete

context.Widgets
    .Where(w => w.WidgetId == widgetId)
    .ExecuteDelete();
Run Code Online (Sandbox Code Playgroud)

这里需要注意的是,根据其文档,ExecuteDelete不需要 a :SaveChanges

此操作立即针对数据库执行,而不是推迟到调用 DbContext.SaveChanges() 为止。它还不会以任何方式与 EF 更改跟踪器交互:调用此操作时恰好被跟踪的实体实例不会被考虑在内,也不会更新以反映更改。

我知道这个问题是针对 EF4 提出的,但如果您升级的话,这是一个不错的选择!


Mar*_*son 30

对于使用EF5的任何人,可以使用以下扩展库:https://github.com/loresoft/EntityFramework.Extended

context.Widgets.Delete(w => w.WidgetId == widgetId);
Run Code Online (Sandbox Code Playgroud)

  • 在大型表上存在性能问题,在我的情况下无法使用. (3认同)
  • 我强烈不推荐使用这个库。虽然它有方法的异步版本,但它们并不是真正的异步。它只是同步方法的包装。在高负载环境中使用这个库可能会遇到很多意想不到的问题。 (3认同)

Luk*_* Vo 22

这个答案适用于 EF Core 7(我不知道他们现在是否将 EF Core 与 EF 合并,在他们将两者分开之前)。

EF Core 7 现在支持ExecuteUpdate 和 ExecuteDelete(批量更新)

// Delete all Tags (BE CAREFUL!)
await context.Tags.ExecuteDeleteAsync();

// Delete Tags with a condition
await context.Tags.Where(t => t.Text.Contains(".NET")).ExecuteDeleteAsync();
Run Code Online (Sandbox Code Playgroud)

等效的 SQL 查询是:

DELETE FROM [t]
FROM [Tags] AS [t]

DELETE FROM [t]
FROM [Tags] AS [t]
WHERE [t].[Text] LIKE N'%.NET%'
Run Code Online (Sandbox Code Playgroud)


Ngu*_*anh 16

实体框架核心

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();
}
Run Code Online (Sandbox Code Playgroud)

总结

从集合底层的上下文中删除给定的实体集合,每个实体都被置于已删除状态,以便在调用 SaveChanges 时将其从数据库中删除。

备注

请注意,如果 System.Data.Entity.Infrastructure.DbContextConfiguration.AutoDetectChangesEnabled 设置为 true(这是默认值),则 DetectChanges 将在删除任何实体之前调用一次,并且不会再次调用。这意味着在某些情况下,RemoveRange 的性能可能比多次调用 Remove 的性能要好得多。请注意,如果上下文中存在任何处于已添加状态的实体,则此方法将使其与上下文分离。这是因为假定已添加实体不存在于数据库中,因此尝试删除它是没有意义的。


Edw*_*rey 11

仍然似乎很疯狂,不得不从服务器拉回任何东西只是为了删除它,但至少回到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 });
Run Code Online (Sandbox Code Playgroud)


jzm*_*jzm 10

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();
} 
Run Code Online (Sandbox Code Playgroud)

用法:

// Delete where condition is met.
DeleteWhere<MyEntity>(d => d.Name == "Something");

Or:

// delete all from entity
DeleteWhere<MyEntity>();
Run Code Online (Sandbox Code Playgroud)

  • 这实际上与db.People.RemoveRange(db.People.Where(x => x.State =="CA"))相同; db.SaveChanges(); 所以没有性能提升. (7认同)

归档时间:

查看次数:

287478 次

最近记录:

6 年,7 月 前