ASP.NET Core Identity:如何使用 EF Core 事务?

ASP.NET Core Identity: How to Use EF Core Transactions?

提问人:Emre Bener 提问时间:11/9/2023 最后编辑:Emre Bener 更新时间:11/9/2023 访问量:37

问:

我在项目中使用 EF Core,并且希望在使用内置 Identity 方法(例如 或)时实现原子性。这是我的用例:CreateAsyncAddClaimsAsync

#region CREATE USER & SET ROLE

var newUser = new ApplicationUser
{
    // set user properties here...              
};

var result1 = await userManager.CreateAsync(newUser);

var result2 = await userManager.AddClaimsAsync(newUser, new Claim[]{
                new Claim("asSuperUser", "asSuperUser")
});
#endregion

#region SEND ACCOUNT INITIALIZATION EMAIL

if (result1.Succeeded && result2.Succeeded)
{
    // continue if everything went fine...

当然,如果一切顺利,没有问题,但是如果 Identity 无法向添加的用户添加声明(方法失败)怎么办?这将是个坏消息,因为整个项目都围绕着CBAC(基于声明的访问控制)展开,因此确保原子性在这里至关重要。我的问题是:在使用 Identity 的内置方法(例如 or )时,有没有办法利用 EF Core 事务,其中我不是通过 DI 直接使用 DbContext,而是使用各种 Identity 服务(例如)从而间接访问 DbContext?AddClaimsAsyncCreateAsyncAddClaimsAsyncuserManager

如果我使用多个 Identity 服务(例如组合 和 ,其中每个作用域服务实例可能会创建自己的 DbContext 实例),事情会变得更加复杂。SignInManagerUserManager

asp.net 实体框架 asp.net-core 实体框架核心 asp.net-identity

评论

0赞 Panagiotis Kanavos 11/9/2023
只需在最后打一次电话。没有 EF Core 事务,EF Core 不是数据库驱动程序。EF Core 仍然使用工作单元模式。DbContext 跟踪内存中的所有更改,并在最后(在内部数据库事务中调用 时)保留所有更改SaveChangesSaveChanges
0赞 Emre Bener 11/9/2023
@PanagiotisKanavos如果我依赖更改跟踪器,情况就会如此,但这里的情况并非如此。右?每个 Identity 服务方法都可自行工作,并在内部应用更改。我什至没有在这里直接访问 DbContext。
0赞 Panagiotis Kanavos 11/9/2023
就是这样,时期。标识与存储无关,实际存储可能不是数据库或不使用 EF Core。在您的例子中,您可以将整个代码包装在 TransactionScope 中。这会在单个长期数据库事务中登记所有 ADO.NET 连接。在超出范围之前调用 Commit 以提交所有更改。不过,这将使连接在 Transactionscope 期间保持打开状态,以及所采取的任何锁。这会导致更多的阻塞并损害可扩展性
1赞 Panagiotis Kanavos 11/9/2023
您需要记住,如果服务使用不同的数据库,TransactionScope 将尝试创建分布式事务。如果数据库存储在同一个数据库实例上,这在 SQL Server 中不会有问题。SQL Server 仍将使用正常的跨数据库事务。如果数据库位于不同的服务器上,或者您使用不同的数据库,则 TransactionScope 将启动分布式事务。

答:

0赞 Emre Bener 11/9/2023 #1

事实证明,EF Core 事务可以使用标识服务方法。例:

using var transaction = await context.Database.BeginTransactionAsync();

// CREATE USER
var result1 = await userManager.CreateAsync(superUser);
if (!result1.Succeeded)
{
                await transaction.RollbackAsync();
                var errors1 = result1.Errors.Select(error => error.Description);
                return Json(new { success = false, error = string.Join(", ", errors1) });
}

// ASSIGN RELEVANT ROLE
var result2 = await userManager.AddClaimsAsync(superUser, new Claim[]{
                new Claim("asSuperUser", "asSuperUser")
});
if (!result2.Succeeded)
{
                await transaction.RollbackAsync();
                var errors2 = result2.Errors.Select(error => error.Description);
                return Json(new { success = false, error = string.Join(", ", errors2) });
}

// await context.SaveChangesAsync(); you would only need this if you relied on change tracker, which is not the case here
await transaction.CommitAsync();

return Json(new { success = true });

评论

1赞 Panagiotis Kanavos 11/9/2023
这是我不想发布的“肮脏”方式。可以使用 TransactionScope,而不是尝试显式访问作用域的 DbContext。您也不需要任何调用,只需退出事务范围即可。释放 DbTransaction 会导致回滚。DbTransacion 和 DbContext 在此处使用相同的模式 - 除非显式提交,否则所有内容都将被丢弃RollBack
1赞 Panagiotis Kanavos 11/9/2023
你也不需要最终的,除非你自己做了任何修改。您发布此问题的真正原因是,并在内部致电。SaveChangesCreateUserAsyncAddClaimsAsyncSaveChanges