【问题标题】:EF Core: Update thousands of records failedEF Core:更新数千条记录失败
【发布时间】:2021-08-26 16:44:19
【问题描述】:

我正在使用 EF Core 更新名为 MyTable 的表中的数千条记录。它已经有 1.500.000 行。下面是我用来更新其属性之一的简单代码 MyProp 为每条记录使用不同的值:

using (MyDbContext ctx = new MyDbContext())
{
      var rows = ctx.Set<MyTable>().Take(1000000).ToList(); // I only take the first 1 million rows
      int idx = 0;
      foreach (var row in rows)
      {
           row.MyProp = $"MyNewValue{idx++}";
      }
      ctx.SaveChanges();
}

如果我用一百万行来更新它们,在调用 ctx.SaveChanges() 时,我得到以下异常:预计读取 4 个标头字节,但只收到 0 个。 em>(请参阅下面的完整堆栈跟踪)

我不知道为什么我会遇到这样的异常,如果我取 1000000 行,但如果我只取 750000 则不会。 我读到它可能与 SQL 服务器的超时有关:下面列出,但我真的不知道它是否真的与一个超时有关以及如何找到解决方案。我应该将更新分成几个并调用几次 SaveChanges() 吗?

感谢您的任何建议。

以下是 MySql 服务器的超时时间:

connect_timeout 10

delayed_insert_timeout 300

have_statement_timeout 是的

innodb_flush_log_at_timeout 1

innodb_lock_wait_timeout 50

innodb_print_lock_wait_timeout_info 关闭

innodb_rollback_on_timeout 关闭

interactive_timeout 610

lock_wait_timeout 31536000

net_read_timeout 30

net_write_timeout 60

rpl_stop_slave_timeout 31536000

slave_net_timeout 60

thread_pool_idle_timeout 60

wait_timeout 610

这是完整的堆栈跟踪: 来自 Microsoft.EntityFrameworkCore.Update:错误保存上下文类型“MyDbContext”的更改时数据库中发生异常。 Microsoft.EntityFrameworkCore.DbUpdateException:更新条目时出错。有关详细信息,请参阅内部异常。 ---> MySql.Data.MySqlClient.MySqlException (0x80004005): 读取结果集失败。 ---> System.IO.EndOfStreamException:预期读取 4 个标头字节,但只收到 0。 在 C:\projects\mysqlconnector\src\MySqlConnector\Protocol\Serialization\ProtocolUtility.cs 中的 MySqlConnector.Protocol.Serialization.ProtocolUtility.DoReadPayloadAsync(BufferedByteReader bufferedByteReader, IByteHandler byteHandler, Func1 getNextSequenceNumber, ArraySegmentHolder1 previousPayloads, ProtocolErrorBehavior protocolErrorBehavior, IOBehavior ioBehavior):第 462 行 在 C:\projects\mysqlconnector\src\MySqlConnector\Protocol\Serialization\StandardPayloadHandler.cs:line 37 在 C:\projects\mysqlconnector\src\MySqlConnector\Core\ServerSession.cs:line 665 中的 MySqlConnector.Core.ServerSession.ReceiveReplyAsync(IOBehavior ioBehavior, CancellationToken cancelToken) 处

【问题讨论】:

  • 所以,这是已知的EF的弱点。并且有很多解决方案可以通过第三方库在没有 ChangeTracker 的情况下做到这一点。
  • 谢谢,但为什么会向我发送此错误?你说的弱点是什么?我搜索批量库,但它们不是免费的。正如您在以下回复中提到的,我将尝试使用 FromSqlRaw 使用直接原始语句。谢谢
  • 更新问题,需要更新不同值的记录。没有那么多图书馆可以做到这一点。
  • 我更新了问题,但是如果我使用 FromSqlRaw 更新 100 万条记录,我将需要调用 FromSqlRaw 100 万次,因为我无法编写单个更新 SQL 查询来更新 100 万条记录。一次具有不同值的记录,除非我需要插入 100 万条记录,因为我可以插入到单个 sql 查询中?
  • 没错,将准备样本如何尽可能快地更新百万条记录。

标签: exception entity-framework-core


【解决方案1】:

对于此类更新,最好使用将保存新值的临时表。然后通常应该通过连接到临时表来更新记录。

EF Core linq2db.EntityFrameworkCore 有扩展名(请注意,我是创建者之一)。它将为 LINQ 查询添加临时表支持,并可以在不离开类型安全 LINQ 的情况下进行此类更新。

// definition of temporary table structure
class IntermediateTable
{
    public int Id {get; set;}
    public string MyProp {get; set;}
}
var items = ... // some input list
var rowsToUpdate = items.Select(x => new IntermediateTable 
{
    Id = x.Id,
    MyProp = x.MyProp
});

using var ctx = new MyDbContext();
using var db = ctx.CreateLinqToDBConnection();

// here all records are populated to database as fast as possible
using var tempTable = db.CreateTempTable(rowsToUpdate, 
   options: new BulkCopyOptions { BulkCopyType = BulkCopyType.ProviderSpecific },
   tableName: "SomeTempTableName");

// preparing update query
var queryToUpdate = 
    from x in ctx.Set<MyTable>()
    join t in tempTable on x.Id equals t.Id
    select new {x, t};

queryToUpdate
    .Set(u => u.x.MyProp, u => u.t.MyProp)
    .Update();

最后临时表会被自动删除。

【讨论】:

    【解决方案2】:

    对于您的用例,因为您想设置一个显式值,您可以执行一些不会在服务器上返回任何行的直接 sql。

    读入this nice chapter,您实际上可以将整个操作转换为仅在db 上运行的简单sql 语句。 分页(跳过)部分将被移动到数据库中,不会检索任何数据以节省性能并且整个操作不会消耗服务器上的内存

    【讨论】:

    • 这一章有什么有趣的地方?如何执行原始 SQL?
    • 嗨@SvyatoslavDanyliv,谢谢你的建议。我将尝试使用 .FromSqlRaw 方法发送一条 SQL 语句。但实际上,我需要从具有不同值的实体中更新所有 'MyProp' 属性,所以我想我必须发送 100 万条 FromSqlRaw 指令,每个指令都有不同的 'UPDATE MyTable Set MyProp='Value for EntityX''?而且我还必须做很多插入,我会尝试使用 FromSqlRaw,但由于我们可以在单个 INSERT INTO 语句中插入多条记录,我想我只能调用一次 FromSqlRaw。我说的对吗?
    • 嗨@MKougiouris,谢谢,我会尝试使用FromSqlRaw 方法。您所说的分页(跳过)部分是什么?如果我想用 100 万个不同的值更新 100 万条记录,我必须用不同的 'UPDATE MyTable SET MyProp='MyValueXX' 调用 100 万次 'FromSqlRaw',对吗?但是如果我需要插入(或删除)100 万条记录,我只需要调用 FromSqlRaw 一次,因为我可以在同一个 SQL INSERT INTO 指令中指定多条记录,对吗?还是谢谢你的回复
    • 如果您想批量更新许多对象,最好为此编写一个 sql 查询并直接执行它。如果您需要 1 个或 2 个或 3 个查询来执行此操作,这取决于您...跳过我的意思是对此感到抱歉
    猜你喜欢
    • 1970-01-01
    • 2020-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-18
    相关资源
    最近更新 更多