我正在使用 EF core 6.0 开发 .NET 6 项目。该项目是使用 Clean Architecture 和 CQRS 模式构建的。
我的问题比较笼统,我更多地寻求意见而不是直接答案。
想象一下我有一个从数据库中删除用户的命令。我们称之为
DeleteUserByIdCommand
。该命令获取用户 ID 并检查用户是否存在,如果用户存在,则将其删除。
在命令末尾,我调用
context.SaveChanges()
,因此删除实际上发生在数据库中。当我添加另一个命令时,问题就出现了,我们将其命名为DeleteUsersByIdBulkCommand
。在这个命令中,为了不重复自己,我想使用我现有的DeleteUserByIdCommand
。新命令采用用户 ID 数组。不过,这样一来,如果我要删除 100 个用户,则 SaveChanges()
和 get 请求将执行 100 次,这对性能来说非常糟糕。
对我来说只有两个简单的选择,我都尝试过:
DeleteUserByIdCommand
并在新代码中编写新代码。问题是,例如,如果我想扩展命令,比如说从其他数据库或第三方 API 中删除用户,我将不得不在两个地方更改代码。DeleteUserByIdCommand
中有一个称为 ShouldCallSaveChanges
的属性,它是布尔值。默认情况下它是 true,但是当我从 DeleteUsersByIdBulkCommand
调用此命令时,它将设置为 false,并且我将在 foreach 之后执行 SaveChanges()
。我的问题是对于这样的情况是否有一些严格的规则?在这里重复代码是否有问题,或者对于这种情况可能有一些特定的解决方案。我想听听您的意见或您可能有的其他建议:)
这是一般问题的一个例子,“为什么我的批量数据操作语句如此慢?”典型 RDBMS 服务器的一般答案是“因为数据操作的大部分工作发生在提交阶段”。
在纯 SQL 应用程序中,这意味着您应该执行如下所示的一系列操作来有效删除多行:
BEGIN TRANSACTION;
DELETE FROM mytable WHERE whatever;
DELETE FROM mytable WHERE whatever;
DELETE FROM mytable WHERE whatever;
COMMIT;
当您处于事务中时,RDBMS 会对操作进行批处理,当您提交时,它会完成应用这些操作的所有工作。 (这是一个过于简单化的说法,但对于涉及少于数万次操作的几乎所有应用程序来说,这是一个非常有用的心理模型。)
这如何转化为 EF Core? .SaveChanges() 文档是这样说的:
对于大多数数据库提供商来说,SaveChanges 是事务性的。
您提到的代码,通过在每次操作后调用
.SaveChanges()
,隐式地为每个操作执行一个事务。这使得您的代码易于调用者使用。但对于像这样的批量操作来说,它不太适合
foreach (id in arrayOfIds) {
ctx.DeleteUserById(id);
}
构建代码的最简单方法(无需构建某种神奇的批量模式属性)是始终显式调用 .SaveChanges(),而不是使其隐式。
foreach (var id in arrayOfIds) {
ctx.DeleteUserById(id);
}
ctx.SaveChanges();
这将与重写代码一样快(除非您要立即删除成千上万的用户)。但你必须记住调用.SaveChanges()。
当我做这些事情时,我实现了一个实现 IDisposable 的更新器类,并在其 Dispose 方法中调用 .SaveChanges。
然后我会做这样的事情。
using (var upd = new MyUpdater()) {
upd.DeleteUserById (whatever);
upd.AddUser (whatever);
upd.ChangeUser (whatever);
}
当我的 MyUpdater 实例超出使用范围时,它的 .Dispose 方法会被自动调用。如果您想要这样做,这还有一个额外的好处,即使用单个数据库事务来执行一堆不同的操作。
对 .Dispose() 的超出范围的调用总是会发生,即使某处存在异常。