简介
我正在开发一个带有 SQL Server 应用程序的 ASP.NET Core,它需要对所有记录进行软删除。
我使用显式
BeginTransaction()
,以防实体被软删除,以便所有相关实体也可以被删除。
背景
我不只使用
SaveChanges()
隐式执行此操作的原因是当我使用 FromSqlInteroplated
使用某些存储过程来检查实体是否具有任何相关(非软删除)实体时;结果看起来像是来自实际的数据库,但它并没有按照我想要的方式工作,因为我想在进行存储过程调用之前保留软删除更改。
而当我在每次删除操作后使用带有
BeginTransaction()
和 SaveChanges
的显式事务时,存储过程会按我想要的方式工作,即使在每次 SaveChanges
时,更改不会持久化到实际数据库中,而是会进行对照上下文,这是怎么发生的?
问题
抱歉,如果这看起来是一个愚蠢的问题,如果调用
SaveChanges
不会在显式未提交的事务上下文中持久保存对实际数据库的更改,在这种情况下,带有 FromSqlInteroplated
的存储过程如何工作,存储过程查询是针对实际的数据库还是上下文?
通过显式事务,对
SaveChanges
的调用会写入数据库,并且只要您使用该 DbContext 对数据库执行任何查询,它都会看到它们。该事务范围之外的查询会发生什么情况将取决于数据库的隔离级别和相关的外部查询。例如,如果您的数据库使用典型的 READ COMMITTED 隔离级别,则在事务打开时,外部查询将被阻止,直到事务提交或回滚。如果数据库使用诸如 READ COMMITTED SNAPSHOT 之类的东西(通常推荐用于 EF 和其他 ORM),那么这些第三方查询将不会被阻止,它们只会返回不包括事务所做更改的数据。
例如,如果我有父/子关系并运行以下命令:
using var context = new SoftDeleteDbContext();
using(var tx = context.Database.BeginTransaction());
var parent = context.Parents.Include(x=>x.Children).First(x => x.ParentId == 1);
var newChild = new Child("Dameon", 12, parent);
parent.Children.Add(newChild);
context.SaveChanges();
var data = context.Children.AsNoTracking().Where(x => x.ParentId == 1).ToList();
tx.Rollback(); // <-- Breakpoint Here
在回滚上设置断点,如果我检查
data
的结果,我将看到我的新子“Dameon”已返回。 AsNoTracking()
强制查询数据库。如果您在该事务仍在运行时进入 SSMS 并运行 SELECT * FROM Children WHERE ParentId = 1
,您将看到除“Dameon”之外的所有子项都将被返回。在交易提交之前,他不会出现在这些结果中。回滚后,他将不会出现在任何一个查询中。如果您不使用 SNAPSHOT 隔离,则 SSMS 查询将被锁阻塞,直到事务提交或回滚。
如果您想证明该行在数据库中,您可以使用
SELECT * FROM Children WHERE ParentId = 1 WITH (UNCOMMITTED)
,然后会出现“Dameon”。