【问题标题】:Bulk Update in Entity Framework CoreEntity Framework Core 中的批量更新
【发布时间】:2017-09-17 19:12:58
【问题描述】:

我从数据库中提取了一堆时间表条目并使用它们来创建发票。保存发票并获得 ID 后,我想用发票 ID 更新时间表条目。有没有一种方法可以批量更新实体而不一次加载一个?

void SaveInvoice(Invoice invoice, int[] timeEntryIds) {
    context.Invoices.Add(invoice);
    context.SaveChanges();

    // Is there anything like?
    context.TimeEntries
        .Where(te => timeEntryIds.Contains(te.Id))
        .Update(te => te.InvoiceId = invoice.Id);
}

【问题讨论】:

标签: c# .net entity-framework entity-framework-core


【解决方案1】:

如果TimeEntryInvoice 有关联(检查导航属性),您可能可以这样做:

var timeEntries = context.TimeEntries.Where(t => timeEntryIds.Contains(te.Id)).ToArray();

foreach(var timeEntry in timeEntries)
    invoice.TimeEntries.Add(timeEntry);

context.Invoices.Add(invoice);

//save the entire context and takes care of the ids
context.SaveChanges();

【讨论】:

  • 我没有这样做,而是在保存之前将 TimeEntries 添加到发票中。 invoice.TimeEntries = context.TimeEntries.Where(te => timeEntryIds.Contains(te.Id)).ToArray()
【解决方案2】:

您是否追求简化语法的表现?

我建议使用直接 SQL 查询,

 string query = "Update TimeEntries Set InvoiceId = <invoiceId> Where Id in (comma separated ids)";    
 context.Database.ExecuteSqlCommandAsync(query);

对于逗号分隔的 ID,您可以使用 string.Join(',', timeEntryIds)

这取决于您实际需要什么。如果你想使用 Linq,那么你需要遍历每个对象。

【讨论】:

  • SQL注入攻击!!每个使用 EF 的人都知道他们可以触发 SQL,但不建议这样做。
【解决方案3】:

免责声明:我是项目的所有者Entity Framework Plus

我们的图书馆有一个批量更新功能,我相信这正是您想要的

此功能支持 EF Core

// Is there anything like? YES!!!
context.TimeEntries
    .Where(te => timeEntryIds.Contains(te.Id))
    .Update(te => new TimeEntry() { InvoiceId = invoice.Id });

维基:EF Batch Update

编辑:回复评论

它是否支持包含您的示例中的内容?我认为这来自 EF Core,它甚至在 3.1 版本中都不受支持

EF Core 3.x 支持包含:https://dotnetfiddle.net/DAdIO2

编辑:回复评论

这很好,但这需要类的零参数公共构造函数。这不是一个伟大的。有什么办法可以解决这个问题?

从 EF Core 3.x 开始支持匿名类型

context.TimeEntries
    .Where(te => timeEntryIds.Contains(te.Id))
    .Update(te => new { InvoiceId = invoice.Id });

在线示例:https://dotnetfiddle.net/MAnPvw

【讨论】:

  • 这很好,但这需要类的零参数公共构造函数。这不是一个伟大的。有什么办法可以解决这个问题?
【解决方案4】:

在实体框架核心中,您可以使用update range method。您可以看到一些示例用法 here

using (var context = new YourContext())
{
     context.UpdateRange(yourModifiedEntities);

     // or the followings are also valid
     //context.UpdateRange(yourModifiedEntity1, yourModifiedEntity2, yourModifiedEntity3);
    //context.YourEntity.UpdateRange(yourModifiedEntities);
    //context.YourEntity.UpdateRange(yourModifiedEntity1, yourModifiedEntity2,yourModifiedEntity3);

    context.SaveChanges();
  }

【讨论】:

  • 请注意,虽然 EF 通过为每个实体执行一条 UPDATE 语句来提高性能,但它不会像上面 @Dhanuka777 的示例那样运行一次更新所有实体的单个 UPDATE 语句。后者要快得多。
  • @SandorDrieënhuizen 我不明白如何执行每个实体的 UPDATE 语句比单个 UPDATE 更快。所以你说运行更新 10.000 次比运行一次快吗?来吧。
  • @schlingel:即使您只调用一个 UpdateRange 命令,EF 也会在后台为每个实体生成一条 SQL UPDATE 语句。因此,您仍然可以有效地执行大量单独的更新查询,这比单个组合更新查询要慢得多。
【解决方案5】:

Entity Framework Core 5.0 中引入的IQueryable.ToQueryString 方法可能有助于解决这种情况。此方法将生成可包含在原始 SQL 查询中的 SQL,以对该查询标识的记录执行批量更新。

例如:

void SaveInvoice(Invoice invoice, int[] timeEntryIds) {
    context.Invoices.Add(invoice);
    context.SaveChanges();

    var query = context.TimeEntries
        .Where(te => timeEntryIds.Contains(te.Id))
        .Select(te => te.Id);

    var sql = $"UPDATE TimeEntries SET InvoiceId = {{0}} WHERE Id IN ({query.ToQueryString()})";

    context.Database.ExecuteSqlRaw(sql, invoice.Id);
}

这种方法的主要缺点是最终会在代码中出现原始 SQL。但是,我不知道有任何合理的方法可以使用当前的 Entity Framework Core 功能来避免这种情况 - 您被这个警告或此处发布的其他答案的警告所困扰,例如:

  • 引入对另一个库的依赖,例如 Entity Framework PlusELinq
  • 使用 DbContext.SaveChanges() 将涉及执行多个 SQL 查询来一次检索和更新一条记录,而不是进行批量更新。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-06
    • 1970-01-01
    • 1970-01-01
    • 2020-04-28
    • 1970-01-01
    • 2017-05-05
    • 1970-01-01
    • 2017-02-11
    相关资源
    最近更新 更多