如何在实体框架中删除多行(无foreach)


305

我正在使用实体框架从表中删除多个项目。没有外键/父对象,因此我无法使用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
您可能需要重新访问已接受的答案。
Eric J.

1
如果您想保持表现,也许您想在这里查看我的答案stackoverflow.com/a/35033286/274589
Adi 2016年

Answers:


49

如果您不想执行SQL,则最好在循环中直接调用DeleteObject。

但是,您可以执行SQL,并且仍然可以使用我在此介绍的方法通过扩展方法使其完全通用。

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


ExecuteStoreCommand不是正确的方法.DeleteAllSubmit在linq to sql中工作,但不在实体框架中工作。我想要实体框架中的相同选项。
2013年

653

使用EntityFramework 6可以使此操作变得容易一些.RemoveRange()

例:

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

31
这正是我们所需要的。。。除非在足够大的范围内使用它,否则我会遇到内存不足的异常!我认为RemoveRange的重点是将处理传递给数据库,但显然不是。
Samer Adra 2014年

这比为每个实体设置Deleted状态快WAAAYYYY!
Jerther 2014年

54
可以肯定的是,这个答案比较容易,但是从性能角度来看可能不是很好。为什么?这个巧妙的方法与在foreach循环中删除它的方法相同,它首先获取所有行,然后逐个删除,唯一的好处是保存“ DetectChanges在删除任何实体之前将被调用一次,而不会再次被调用”一样,请尝试使用工具查看生成的sql。
Anshul Nigam 2014年

6
对于一个足够大的范围内,你可以试试。取(10000)和中循环,直到RemoveRange(...)计数()== 0。
埃里克·J。

20
问题在于,RemoveRange输入参数是IEnumerable,因此要执行删除操作,它会枚举所有实体,并为每个实体运行1个DELETE查询。
布比

74

这样就好了吧?我可以使用扩展方法或辅助程序对其进行抽象,但是在某个地方我们仍将进行foreach,对吗?

好吧,是的,除了可以将它分为两​​类:

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

76
您正在做一个ToList(),它达到了目的。与原始解决方案有何不同?
lahsrah

3
我有问题,因为我在上下文对象中只有Remove方法。
Pnct

2
当预期要有一百万行(甚至几百行)时,这绝对不是合适的解决方案。但是,如果我们确定只有几行,则此解决方案会很整洁,效果很好。是的,这将涉及到数据库的几次往返,但是在我看来,调用SQL所涉及的抽象丢失直接超过了好处。
Yogster 2014年

顾名思义,实体框架最适合实体级别的数据。批量数据操作最好由良好的旧存储过程处理。在性能方面,它们是迄今为止最好的选择,它将击败需要循环的所有EF逻辑。
佩斯曼

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

但是,如何使用ID列表来做到这一点呢?此解决方案不能很好地处理“列表”。
JesseNewman16年

11
@ JesseNewman19如果您已经有ID列表,请使用WHERE IN ({0}),然后第二个参数应为String.Join(",", idList)
兰登

@Langdon将不起作用,因为它将像这样将命令发送到sql:WHERE IN(“ 1、2、3”)。然后,数据库会引发错误,因为您向它传递了一个字符串而不是整数列表。
JesseNewman16年

我希望使用LINQ生成这样的声明。我发现最接近的东西是一个lib。EntityFramework.Extended
贾德

如果使用String.Join,则可能需要使用string.Format并将已经形成的SQL字符串传递给命令。只要您的列表中只有整数,就没有注入攻击的风险。检查以下问题:如何将数组传递给execute store命令?
安德鲁

50

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

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


什么是DbContext?我假设您是自动生成的实体框架上下文?我没有称为Set <T>的方法。
隐身犹太教

@Stealth:是的,这是您的EF数据上下文,我使用代码优先,但自动生成的上下文应该相同。很抱歉输入错误的语句应该是Set <T>()(我的公司限制了互联网访问,我无法粘贴代码,必须手动键入...),代码已更新:)
Thanh Nguyen

3
这是实际回答问题的唯一答案!每隔一个答案一次就删除一个项目,这真是令人难以置信。
罗克兰'16

这感觉像是最正确的答案。它允许以一种非常通用的方式进行删除,并且可以将工作正确地卸载到数据库而不是C#。
JesseNewman16年

1
对于所有技术水平较低的程序员,我想详细说明如何实现这种出色的通用解决方案,因为这可以节省我几分钟的时间!继续在下
一条

30

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

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

3
大表有性能问题,在我的情况下不可用。
2014年

@Tomas发出了什么样的表演?问题有多严重,表格有多大?其他人可以确认吗?
Anestis Kivranoglou,

与那里的替代方案相比,它确实非常快
Jaider

Delete()在EF6 中看不到实体中的功能。
dotNET 2016年

context.Widgets.Where(w => w.WidgetId == widgetId).Delete();与EntityFramework.Extended较新的方式
彼得·科尔

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

请注意-这可能会使Entity Framework的实体验证失败,因为您的存根Widget对象仅具有已初始化的Id属性。解决该问题的方法是使用context.Configuration.ValidateOnSaveEnabled = false(至少在EF6中)。这将禁用Entity Framework自己的验证,但是当然仍然会执行数据库自己的验证。
Sammy S.19年

@SammyS。我还没有经历过,所以无法详细说明,但是无论如何,EF在删除行时都会烦恼验证。
爱德华·布雷

你是完全正确的。我deleteupdateing实体而不加载它们的类似解决方法感到困惑。
Sammy S.19年

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

7
这实际上与db.People.RemoveRange(db.People.Where(x => x.State ==“ CA”))相同;db.SaveChanges(); 因此没有性能提升。
ReinierDG

4

对于EF 4.1,

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

1
这行得通,但是使用实体框架的全部目的是采用一种面向对象的方式与数据库进行交互。这只是直接运行SQL查询。
Arturo TorresSánchez2015年

4

您可以使用扩展库来执行此操作,例如EntityFramework.Extended或Z.EntityFramework.Plus.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操作生成器支付600美元以上的费用。认真吗
nicolay.anykienko

@ nicolay.anykienko当我使用它时,该图书馆是免费的,您需要支付其他操作费用,对不,我不知道您是否必须支付费用
UUHHIVS

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

现在,有趣的部分是删除项目并使用扩展名更新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);
    }
}

感谢您提供一个很好的示例,该示例使用存储过程,然后通过使用代码将其实现为扩展。
glenn garson 2015年

3

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

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

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


3
您还应该提到,不能truncate table在FOREIGN KEY约束所引用的表上运行。(您可以截断具有引用自身的外键的表。)MSDN文档
宽带

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

2

实体框架核心

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(默认设置),则DeleteChanges将在删除任何实体之前被调用一次,并且不会再次被调用。这意味着在某些情况下,RemoveRange的性能可能比多次调用Remove会好得多。请注意,如果任何实体以“添加”状态存在于上下文中,则此方法将导致其与上下文分离。这是因为假定添加的实体在数据库中不存在,因此尝试删除它是没有意义的。


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

鉴于EF不正确支持删除条件的映射,这可能是完成工作的最佳选择。
Tony O'Hagan

1

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

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

context.Widgets.DeleteAllOnSubmit(widgetList);

context.SubmitChanges();

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


3
看起来您误会了什么var
自由-m

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

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


0

EF 6。=>

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

0

最好 : in EF6 => .RemoveRange()

例:

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

14
这与凯尔的答案有何不同?

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

1
您的答案与user1308743答案有何不同?
Sergey Berezovskiy 2014年

我只是在分享一个可行的例子。我能做些什么来回馈我的帮助。
Brian Quinn 2014年

-3

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

1
对于原始EF 6.2,这是不可能的。也许您正在使用Z.EntityFramework.Plus或类似的东西?(entityframework.net/batch-delete
塞米S.

第一个是原始的EF 6.2,并且可以找到。正如我提到的,第二个是使用LINQKit。
弗拉基米尔·

1
嗯,我找不到这种方法。您可以检查此方法驻留在哪个类和哪个命名空间中吗?
Sammy S.19年

我认为(Delete()方法本来就不存在)。
总和没有
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.