高效更新

批处理

EF Core 通过在一次往返中自动将所有更新批处理在一起,帮助最大限度地减少往返。 请考虑以下事项:

var blog = await context.Blogs.SingleAsync(b => b.Url == "http://someblog.microsoft.com");
blog.Url = "http://someotherblog.microsoft.com";
context.Add(new Blog { Url = "http://newblog1.microsoft.com" });
context.Add(new Blog { Url = "http://newblog2.microsoft.com" });
await context.SaveChangesAsync();

上述内容从数据库加载博客,更改其 URL,然后添加两个新博客:若要应用此功能,则向数据库发送两个 SQL INSERT 语句和一个 UPDATE 语句。 添加博客实例时,EF Core 不会逐个发送,而是在内部跟踪这些更改,并在调用 SaveChanges 时在单个往返中执行这些更改。

EF 在一次往返中批处理的语句数量取决于所使用的数据库提供程序。 例如,性能分析显示,在涉及少于 4 个语句时,SQL Server 的批处理通常效率较低。 同样,在 SQL Server 中,当语句达到大约 40 条时,批处理的优势会开始降低,因此 EF Core 默认在单个批处理中最多执行 42 条语句,而将其他语句在单独的往返过程中执行。

用户还可以调整这些阈值,以实现可能更高的性能,但在修改这些阈值之前,请谨慎进行基准测试:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseSqlServer(
        @"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True",
        o => o
            .MinBatchSize(1)
            .MaxBatchSize(100));
}

在相关情况下使用 ExecuteUpdate 和 ExecuteDelete

假设你想要为所有员工加薪。 EF Core 中此实现的典型实现如下所示:

foreach (var employee in context.Employees)
{
    employee.Salary += 1000;
}
await context.SaveChangesAsync();

虽然这是完全有效的代码,但让我们从性能角度分析它的作用:

  • 执行一次数据库往返,以加载所有相关员工;请注意,这会将员工的所有行数据带到客户端(即使只需要工资数据)。
  • EF Core 的更改跟踪在加载实体时创建快照,然后将这些快照与实例进行比较,找出哪些属性已更改。
  • 通常,执行第二个数据库往返以保存所有更改(请注意,某些数据库提供程序将更改拆分为多次往返)。 尽管这种批处理行为比每次更新都进行一次数据库往返操作要好得多,但 EF Core 仍然为每个员工发送一条 UPDATE 语句,并且数据库必须分别执行每条语句。

从 EF Core 7.0 开始,可以使用 ExecuteUpdateAsyncExecuteDeleteAsync 方法更高效地执行相同的操作:

await context.Employees.ExecuteUpdateAsync(s => s.SetProperty(e => e.Salary, e => e.Salary + 1000));

这会将以下 SQL 语句发送到数据库:

UPDATE [Employees] SET [Salary] = [Salary] + 1000;

UPDATE 在单个往返中执行整个操作,无需加载或向数据库发送任何实际数据,并且不使用 EF 的更改跟踪机制,这会产生额外的开销。 有关详细信息,请参阅 ExecuteUpdateExecuteDelete

如果使用的 EF Core 版本尚不支持 ExecuteUpdateExecuteDelete,或者想要执行这些方法不支持的复杂 SQL 语句,仍可使用 SQL 查询来执行该操作:

context.Database.ExecuteSql($"UPDATE [Employees] SET [Salary] = [Salary] + 1000");

若要详细了解 SaveChangesExecuteUpdate/ExecuteDelete之间的差异,请参阅 概述页 保存数据。