高效更新

批处理

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

var blog = context.Blogs.Single(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" });
context.SaveChanges();

上述操作从数据库加载博客,更改其 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;
}
context.SaveChanges();

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

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

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

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

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

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

UPDATE 会在单次往返中执行整个操作,而无需加载任何实际数据或将这些数据发送到数据库,也无需使用 EF 的更改跟踪机制(这会增加额外的开销)。 有关详细信息,请参阅 ExecuteUpdateExecuteDelete

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

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

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