【问题标题】:How do I delete multiple rows in Entity Framework Core?如何在 Entity Framework Core 中删除多行?
【发布时间】:2017-01-31 14:36:56
【问题描述】:

我需要使用 Entity Framework Core 从数据库中删除多行。

此代码不起作用:

foreach (var item in items)
{
    myCollection.Remove(item);
}

因为我在第一个对象之后收到错误“InvalidOperationException:集合已修改;枚举操作可能无法执行”。换句话说,.Remove 只删除一个对象。

Entity Framework Core 没有 .RemoveRange,所以我不知道如何执行此操作。

为了保持与各种数据库提供程序的最大兼容性,我不希望调用 context.Database.ExecuteSqlCommand("delete from physical_table where...")。有没有合适的解决方案?谢谢!

【问题讨论】:

  • 请提供minimal reproducible example。目前还不清楚items 和myCollection 变量是什么。仅供参考,DbSet<T> 确实有 RemoveRange 方法。

标签: c# entity-framework-core


【解决方案1】:

因为我在第一个对象之后收到错误“InvalidOperationException:集合已修改;枚举操作可能无法执行”。换句话说,.Remove 只移除一个对象。

这与 EF Core 无关,而且,是的,.Remove() 只删除一个对象。但是,您正在尝试修改您正在迭代的集合。那里有are ways to do this,但这不是一条好路。

Entity Framework Core 没有 .RemoveRange,所以我不知道如何执行此操作。

在 EF Core 中删除多条记录肯定至少有几种简单的方法。而且,EF Core 确实有一个 RemoveRange() 方法 - 它是 DbSet<TEntity> 上的一个方法,请参阅 here in the API docs(如上面的评论中所述)。

几个选项:

  1. 如果myCollection 属于DbSet<TEntity> 的类型,那么像这样的简单调用就可以解决问题:

    _dbContext.MyEntities.RemoveRange(myCollection);
    _dbContext.SaveChanges();
    
  2. 如果myCollection 实际上是您查询的实体的导航属性,您可以在集合上调用.Clear(),而不是迭代和调用.Remove()。

    var myParentEntity = _dbContext.MyParentEntities
                             .Include(x => x.MyChildrenEntities)
                             .Single(x => x.Id == id);
    myParentEntity.MyChildrenEntities.Clear();
    _dbContext.SaveChanges();
    

如上所述,您的问题缺少很多上下文 - 应该发布更完整的代码。我只是在黑暗中试了几次,让你开始使用 EF Core!

【讨论】:

  • 我不确定 Clear 选项在 EF Core 2.0 中是否正常工作。我只是被这个刺痛了。不过,遍历和删除条目效果很好。
  • @Kinetic 你最后发现实际行为了吗?
  • 已经有一段时间了,不,我不记得我在哪里遇到这个问题了。目前,这可能只是 ef core 的另一个限制。如果我的数据集很小,我可能只是坚持使用循环方法,否则就是一个 sql / 存储过程。
  • @Kinetic - 当你这样做时,你还记得它是否在其子级为.Include()'d 的父实体上吗?我还没有这样做,但是,如果您在未跟踪的儿童列表上执行.Clear(),它可能不会做任何事情。
【解决方案2】:

如果您想在某个任意过滤器上删除许多项目(读取数百或更多),最有效的方法是所谓的“批量删除”。 EFCore.BulkExtensions 允许这样做。检查以下示例:

var toRemoveModels = DataAccess.ModelRepository.All
    .Where(m => m.Name.StartsWith("Added model"))
    .ToList();
DataAccess.ModelRepository.BulkDelete(toRemoveModels);

在数据库上下文中的实际实现非常简单:

public void BulkDelete<TModel>(IList<TModel> entities) where TModel: class
{
    this.BulkDelete(entities, bulkConfig: null);
}

这将生成一堆查询,但仍然比发出大量 DELETE 语句更有效:

SELECT [m].[Id], [m].[MakeId], [m].[Name], [m].[PriceInEur]
FROM [Model] AS [m]
WHERE [m].[Name] LIKE N'Added model' + N'%' AND (LEFT([m].[Name], LEN(N'Added model')) = N'Added model')
go
SELECT columnproperty(object_id('dbo.[Model]'),'Id','IsIdentity');
go
SELECT TOP 0 T.[Id] INTO dbo.[ModelTemp208f3efb] FROM dbo.[Model] AS T LEFT JOIN dbo.[Model] AS Source ON 1 = 0;
go
select @@trancount; SET FMTONLY ON select * from dbo.[ModelTemp208f3efb] SET FMTONLY OFF exec ..sp_tablecollations_100 N'[dbo].[ModelTemp208f3efb]'
go
insert bulk dbo.[ModelTemp208f3efb] ([Id] Int)
go
MERGE dbo.[Model] WITH (HOLDLOCK) AS T USING dbo.[ModelTemp208f3efb] AS S ON T.[Id] = S.[Id] WHEN MATCHED THEN DELETE;
go
DROP TABLE dbo.[ModelTemp208f3efb]
go

注意:执行“批量”删除的更有效方法是提供IQueryable,它指定获取项目的方式并生成类似于以下的DELETE :

DELETE FROM SomeTable
WHERE Id IN (SELECT Id FROM SomeTable WHERE ...)

这更快,因为它不需要加载 EF 实体,也不需要创建临时表和 MERGE 反对它。

我使用了 Entity Framework 6 的库,但找不到 EF Core 的非商业库。

【讨论】:

  • 可能值得注意的是 EFCore.BulkExtensions 不支持 MySQL。 github.com/borisdj/EFCore.BulkExtensions/issues/71
  • 核心支持批量操作时为什么要使用外部依赖
  • @johnny5 您能否更清楚地说明如何在 ef core 中没有外部依赖项的情况下进行批量删除?
  • 只是为了展示如何通过查询删除context.Items.Where(a =&gt; a.ItemId &gt; 500).BatchDeleteAsync();
【解决方案3】:

我创建了一个库来批量删除或更新 EF Core 5 上的往返记录。

示例代码如下:

await ctx.DeleteRangeAsync(b => b.Price > n || b.AuthorName == "zack yang");

await ctx.BatchUpdate()
.Set(b => b.Price, b => b.Price + 3)
.Set(b=>b.AuthorName,b=>b.Title.Substring(3,2)+b.AuthorName.ToUpper())
.Set(b => b.PubTime, b => DateTime.Now)
.Where(b => b.Id > n || b.AuthorName.StartsWith("Zack"))
.ExecuteAsync();

Github repository

Report of the library

【讨论】:

    【解决方案4】:

    你可以这样做

    [HttpDelete("{id}")]
    public async Task<ActionResult<string>> DeleteAsset(int id)
    {
        _db.Assets.Remove(_db.Assets.Find(id));
        await _db.SaveChangesAsync();
        // Đi tìm trang table asset_image
        List<AssetImage> assetImageList = _db.AssetImages.Where(x => x.AssetId == id).ToList();
        foreach(AssetImage assetImageItem in assetImageList)
        {
            _db.AssetImages.Remove(assetImageItem);
            _db.SaveChangesAsync();
        }
        return Ok("ok");
    }
    

    【讨论】:

    • 这个例子并没有真正回答问题,但也没有必要多次调用SaveChanges();您可以在方法结束时调用它一次,以减少到数据库的往返。第二个_db.SaveChangesAsync() 也缺少await。最后,如果经常有很多 AssetImage,您最好配置 EFcore 以将资产删除级联到 AssetImage,或者执行原始 sql 删除,这样您只需要一个 db 调用,而不是每个 AssetImage 一个。
    猜你喜欢
    • 2016-12-05
    • 2021-03-29
    • 2017-06-02
    • 2017-03-14
    • 2018-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-28
    相关资源
    最近更新 更多