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

时间:2010-03-25 22:24:26

标签: 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();

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

24 个答案:

答案 0 :(得分:597)

EntityFramework 6使.RemoveRange()更容易实现。

示例:

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

答案 1 :(得分:74)

  

这是好的,对吗?我可以通过扩展来抽象它   方法或助手,但在某个地方,我们仍然会做一个   foreach,对吧?

嗯,是的,除了你可以把它变成一个双线:

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

答案 2 :(得分:68)

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

答案 3 :(得分:47)

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

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

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

答案 4 :(得分:43)

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

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

答案 5 :(得分:30)

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

context.Widgets.Delete(w => w.WidgetId == widgetId);

答案 6 :(得分: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 });

答案 7 :(得分:10)

实体框架核心

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

摘要

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

备注

请注意,如果System.Data.Entity.Infrastructure.DbContextConfiguration.AutoDetectChangesEnabled 设置为true(这是默认值),那么DetectChanges将被调用一次 删除任何实体之前,将不会再次调用。这意味着在某些情况下 这种情况下,RemoveRange的性能可能明显优于调用“移除多个” 时代会做到的。请注意,如果上下文中以“已添加”状态存在任何实体, 那么此方法将导致其与上下文分离。这是因为 假定数据库中不存在添加的实体,因此尝试删除 这没有道理。

答案 8 :(得分: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();
} 

用法:

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

Or:

// delete all from entity
DeleteWhere<MyEntity>();

答案 9 :(得分:4)

对于EF 4.1,

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

答案 10 :(得分:3)

删除的最快方法是使用存储过程。我更喜欢数据库项目中的存储过程而不是动态SQL,因为重命名将被正确处理并且具有编译器错误。动态SQL可以引用已删除/重命名的表,从而导致运行时错误。

在这个例子中,我有两个表List和ListItems。我需要一种快速方法来删除给定列表的所有ListItem。

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

现在是删除项目和使用扩展名更新实体框架的有趣部分。

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 :(得分:3)

您可以使用扩展库来实现,例如EntityFramework.Extended或Z.EntityFramework.Plus.EF6,可用于EF 5,6或Core。当您必须删除或更新并使用LINQ时,这些库具有出色的性能。删除示例(source plus):

ctx.Users.Where(x => x.LastLoginDate < DateTime.Now.AddYears(-2)) .Delete();

或(source extended

context.Users.Where(u => u.FirstName == "firstname") .Delete();

这些使用本机SQL语句,因此性能很好。

答案 12 :(得分:3)

如果要删除表的所有行,可以执行sql命令

using (var context = new DataDb())
{
     context.Database.ExecuteSqlCommand("TRUNCATE TABLE [TableName]");
}

TRUNCATE TABLE(Transact-SQL)从表中删除所有行而不记录单个行删除。 TRUNCATE TABLE类似于没有WHERE子句的DELETE语句;但是,TRUNCATE TABLE更快,并且使用更少的系统和事务日志资源。

答案 13 :(得分:2)

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 :(得分:1)

您还可以使用 DeleteAllOnSubmit()方法将结果传递给通用列表而不是var。这样你的foreach减少到一行代码:

List<Widgets> widgetList = context.Widgets
              .Where(w => w.WidgetId == widgetId).ToList<Widgets>();

context.Widgets.DeleteAllOnSubmit(widgetList);

context.SubmitChanges();

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

答案 15 :(得分:1)

您可以直接执行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(); 
}

答案 16 :(得分:1)

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

这为该组生成了一条删除语句。

答案 17 :(得分:0)

EF 6。=&gt;

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

答案 18 :(得分:0)

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

答案 19 :(得分:0)

如果您使用的是通用存储库:

在通用存储库中,以下可能是新方法。

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

答案 20 :(得分:0)

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

使用示例:

DELETE FROM .... WHERE

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

答案 21 :(得分:-1)

最佳:in EF6 => .RemoveRange()

示例:

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

答案 22 :(得分:-1)

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

答案 23 :(得分:-1)

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

以下是我如何使用它:

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