ChatGPT解决这个技术问题 Extra ChatGPT

如何删除实体框架中的多行(没有 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。我只是想确保我没有遗漏任何东西——这是最好的,对吧?我可以使用扩展方法或帮助器对其进行抽象,但我们仍将在某个地方进行 foreach,对吗?

您可能需要重新访问已接受的答案。
如果您想保持高效,也许您可能想在此处查看我的答案stackoverflow.com/a/35033286/274589
EF Core 的一些答案在 stackoverflow.com/questions/41960215/… 问题中

K
Kyle

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

例子:

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

这正是我们所需要的......除非我在足够大的范围内使用它,否则我会遇到内存不足的异常!我认为 RemoveRange 的全部意义在于将处理传递给数据库,但显然不是。
当然,这个答案更容易,但在性能方面它可能不是很好。为什么?这与在 foreach 循环中删除它完全一样,它首先获取所有行,然后一一删除,唯一的好处是保存“在删除任何实体之前将调用 DetectChanges 并且不会再次调用”休息一样,尝试使用工具查看生成的sql。
对于足够大的范围,尝试类似 .Take(10000) 并循环直到 RemoveRange(...).Count() == 0。
问题是 RemoveRange 输入参数是一个 IEnumerable,因此要执行删除它会枚举所有实体并为每个实体运行 1 个 DELETE 查询。
这似乎是一个非常无效的方法。我在 SQL Profiler 中检查了它:RemoveRange 命令实际上执行 SELECTcommand,而 SaveChanges 对在第一个命令之后找到的每条记录执行 DELETE 命令。在我看来,最好的方法是编写相关的存储过程并从 EF 执行它。
M
Michael Freidgeim
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 是安全的


但是你怎么能用一个 id 列表来做到这一点呢?该解决方案不能很好地处理“列表”。
@JesseNewman19 如果您已经有一个 ID 列表,请使用 WHERE IN ({0}),然后第二个参数应该是 String.Join(",", idList)
@Langdon 这不起作用,因为它会将命令发送到 sql,如下所示:WHERE IN ("1, 2, 3")。然后数据库抛出一个错误,因为您向它传递了一个字符串而不是整数列表。
我希望用 LINQ 生成这样的语句。我发现的最接近的东西是一个库。 EntityFramework.Extended
如果您使用 String.Join,您可能需要使用 string.Format 并将已经形成的 SQL 字符串传递给命令。只要您的列表只有整数,就没有注入攻击的风险。检查这个问题:how can I pass an array to a execute store command?
K
Klaus Byskov Pedersen

这是最好的,对吧?我可以使用扩展方法或帮助器对其进行抽象,但我们仍将在某个地方进行 foreach,对吗?

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

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

你正在做一个 ToList() 失败的目的。这与原始解决方案有何不同?
我有问题,因为我在上下文对象中只有 Remove 方法。
当预期有一百万行(甚至几百行)时,这绝对不是一个合适的解决方案。但是,如果我们确定只有几行,则此解决方案很简洁并且运行良好。是的,这将涉及到数据库的几次往返,但在我看来,直接调用 SQL 所涉及的抽象损失超过了好处。
顾名思义,实体框架最适用于实体级别的数据。批量数据操作最好由良好的旧存储过程处理。就性能而言,它们是迄今为止最好的选择,并且将击败任何需要循环的 EF 逻辑。
C
CarenRose

我知道现在已经很晚了,但如果有人需要一个简单的解决方案,很酷的是你还可以添加 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 的方法。
@Stealth:是的,这是您的 EF 数据上下文,我使用代码优先,但自动生成的上下文应该是相同的。对不起,输入错误的语句应该是 Set() (我的公司限制了互联网访问,我无法粘贴代码,必须手动输入......),代码已更新:)
这是唯一真正回答问题的答案!每个其他答案一次删除每个单独的项目,令人难以置信。
就像 JesseNewman19 说的,我也觉得这是最好的答案。我偶然发现了一种不起作用的情况:当实体与另一个实体具有多对多关系时,不会删除多对多记录。我试图通过反思来掌握这种多对多的关系,但没有成功。任何人都知道如何解决这个问题? (我应该问一个新问题吗?)
对于所有技术含量较低的程序员,我想详细说明如何实现这个出色的通用解决方案,因为它可以为我节省几分钟的时间!在下一条评论中继续...
C
Community

如果您不想执行 SQL,直接在循环中调用 DeleteObject 是您今天可以做的最好的事情。

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

尽管那个答案是针对 3.5 的。对于 4.0,我可能会在底层使用新的 ExecuteStoreCommand API,而不是直接使用 StoreConnection。


ExecuteStoreCommand 不是正确的方法。DeleteAllSubmit 在 linq to sql 中工作,但不在实体框架中。我想要实体框架中的相同选项。
在这个时候(2020 年),这不应该是公认的答案。
a
andrew.fox

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

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

在大型表上存在性能问题,在我的情况下不可用。
@Tomas 你注意到了什么样的表现?问题有多严重,桌子有多大?其他人可以确认吗?
与那里的替代品相比,它真的很快
我在 EF6 的实体中看不到 Delete() 函数。
我强烈不推荐使用这个库。虽然它具有方法的异步版本,但它们不是真正的异步。它只是同步方法的包装。在高负载环境下使用这个库,你会遇到很多意想不到的问题。
N
Nguyen Van Thanh

实体框架核心

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


E
Edward Brey

为了删除它而不得不从服务器上拉回任何东西似乎仍然很疯狂,但至少只取回 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 中)。这会禁用实体框架自己的验证,但当然仍会执行数据库自己的验证。
@SammyS。我没有经历过这种情况,所以无法谈论细节,但 EF 在删除行时会打扰验证似乎很奇怪。
你完全正确。我将 delete 与用于在不加载实体的情况下update 处理实体的类似解决方法混淆了。
P
Prem

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

这实际上与 db.People.RemoveRange(db.People.Where(x => x.State == "CA")); db.SaveChanges();所以没有性能提升。
A
Amit Pawar

对于 EF 4.1,

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

这是可行的,但使用 Entity Framework 的重点在于有一种面向对象的方式与数据库进行交互。这只是直接运行 SQL 查询。
U
UUHHIVS

您可以使用扩展库来执行此操作,例如 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 语句,因此性能非常好。


为批量 sql 操作生成器支付 600 美元以上。严重地?
@nicolay.anykienko 我用的时候,这个库是免费的,还有其他需要付费的操作,对吧,不知道要不要付费
X
Xavier John

最快的删除方法是使用存储过程。我更喜欢数据库项目中的存储过程而不是动态 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

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

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

感谢您提供了使用存储过程的一个很好的示例,然后使用使用代码将其实现为扩展。
A
Amir

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

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

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


您还应该提到,您不能在由 FOREIGN KEY 约束引用的表上运行 truncate table。 (您可以截断具有引用自身的外键的表。)。 MSDN documentation
A
Alexei - check Codidact

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

A
Abhishek Sharma

您可以直接执行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 不正确支持删除条件的映射,这可能是完成工作的最佳选择。
H
Hugo Nava Kopp

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

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

context.Widgets.DeleteAllOnSubmit(widgetList);

context.SubmitChanges();

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


看起来您误解了 var 是什么。
S
Steve Greene

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

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


S
Seagull

我想出了一个很棒的库 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/ 相比有很多好处。 (它们只是同步方法的包装)在高负载环境中使用这个库可能会遇到很多意想不到的问题。


E
Erçin Dedeoğlu

英孚 6.=>

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

L
LOTUSMS

最佳:in EF6 => .RemoveRange()

例子:

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

这与凯尔的回答有何不同?
m
mohammed elshoraky
 context.Widgets.RemoveRange(context.Widgets.Where(w => w.WidgetId == widgetId).ToList());
                db.SaveChanges();

这仅适用于 List 类型。或者可能是一种扩展方法?还是来自 EF Core?
B
Brian Quinn

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

这是我使用它的方式:

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

您的答案与 user1308743 answer 有何不同?
我只是分享一个工作示例。我能做些什么来回馈我在这里得到的帮助。
A
Amarjeet Singh

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

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

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

V
Vladimir

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

对于原始 EF 6.2,这是不可能的。也许您正在使用 Z.EntityFramework.Plus 或类似的东西? (entityframework.net/batch-delete)
第一个是原始的 EF 6.2 并且可以找到。正如我提到的,第二个是使用 LINQKit。
嗯,我找不到这个方法。你能检查一下这个方法驻留在哪个类和哪个命名空间中吗?
我第三点是(Delete() 方法本质上不存在)。