与 ASP.NET Identity UserManager 进行交易的事务

17

我正试图更新一个用户。

AppUserManager appUserManager = HttpContext.GetOwinContext().GetUserManager<AppUserManager>();

AppUser member = await appUserManager.FindByIdAsync(User.Identity.GetUserId());

member.HasScheduledChanges = true;

IdentityResult identityResult = appUserManager.Update(member);

如果Web API的后续调用失败,我需要回滚对用户进行的任何更改。 我知道事务可以做到这一点,就像这样:

using (var context = HttpContext.GetOwinContext().Get<EFDbContext>())
 {
    using (var dbContextTransaction = context.Database.BeginTransaction())
    {      
        try
        {   
            // Changes

            member.HasScheduledChanges = true;

            // Would this be transactional?
            IdentityResult identityResult = appUserManager.Update(member);               

            context.SaveChanges();

            dbContextTransaction.Commit();
        }
        catch //(Exception ex)
        {

            // dbContextTransaction.Rollback(); no need to call this manually.
        }
    }
}

但是在try块中使用AppUserManager执行的操作是否具有事务性?另外,它们是否使用相同的EFDbContext实例?换句话说,我不知道第二个代码示例开头的var context是否会被try块中的appUserManager“Update”方法调用使用。

此外,AppUserManager是这样创建的:

public static AppUserManager Create(IdentityFactoryOptions<AppUserManager> options, IOwinContext context)
{           

    EFDbContext db = context.Get<EFDbContext>();

    AppUserManager manager = new AppUserManager(new UserStore<AppUser>(db));

    // etc.

    return manager;
}
2个回答

20

EFDbContext在你的例子中是相同的 - 在这两种情况下,你都是从OWIN上下文中解析它们,因此这不是一个问题。然而,Identity是以存储无关的方式编写的,这意味着存储机制可以被非SQL Server替换。这就需要在AppUserManager内部缺少事务。所以你需要创建自己的。

我经常在我的生产应用程序中使用var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)(只是稍微多一些架构):

using(var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
    try
    {
        AppUserManager appUserManager = HttpContext.GetOwinContext().GetUserManager<AppUserManager>();

        AppUser member = await appUserManager.FindByIdAsync(User.Identity.GetUserId());

        member.HasScheduledChanges = true;

        IdentityResult identityResult = appUserManager.Update(member);
        scope.Complete();
    }
    catch (Exception ex)
    {
        scope.Dispose();
        throw;
    }
}

谢谢!我的第一个想法是直接使用支持事务的dbContext来操作AspNetUsers表,但我觉得这是不可能的。我认为必须使用UserManager来更新身份系统中的用户,难道没有其他办法吗? - nmit026
3
是的,您可以直接通过dbContext操纵用户记录。这就是AppUserManager所做的,只不过它添加了一些额外的验证。 - trailmax
你是 Owin 和 Identity 的专家,也许你可以看一下这个链接:http://stackoverflow.com/questions/43037450/usermanager-updateuser-method-isnt-thread-safe - nmit026
3
您的异常处理程序中,scope.Dispose() 完全是多余的。 - Michael Brown
@trailmax,为了进一步解释Michael的评论,使用语句将内容包装在try finally中,并调用scope.dispose()。捕获并重新抛出异常除了减缓失败速度外没有任何作用,因为它实际上必须捕获异常。 - Marie
由于它不是异步的,所以在处理dispose期间会阻塞线程。 - Greg

10
使用ASP.NET身份验证UserManager完成事务提交/回滚的完整解决方案。
var appDbContext = HttpContext.GetOwinContext().Get<ApplicationDbContext>();
using (var identitydbContextTransaction = appDbContext.Database.BeginTransaction())
{
   try
   {
       var result = await UserManager.CreateAsync(user, "password");
       if (result.Succeeded)
       {
         var userinfo = await UserManager.FindByNameAsync("Email");
         var userId = user.Id;
         await UserManager.AddToRoleAsync(userId, "rolename");

         identitydbContextTransaction.Commit();
       }
  }
  catch (Exception)
  {
        identitydbContextTransaction.Rollback();
  }
}

它可能会帮助您使用ASP.NET身份UserManager进行交易。但如果交易发生任何错误,它将回滚所有交易,这对我来说是有效的。

这个答案解决了我的问题,我不想使用“Transaction Scope”,而是必须添加对OWIN的引用“using Microsoft.AspNet.Identity.Owin;”。 - Suhail Mumtaz Awan
但是在控制器中这样做是正确的方式吗?带有事务的控制器? - Leandro De Mello Fagundes
有趣,这样做与使用已接受的答案相比有什么优势呢?我也想要做基本相同的事情。 - span
这是一种更简单的方法,让用户在现有的DbContext中自己启动和完成交易 - 允许将多个操作组合在同一个事务中,因此可以作为一个整体进行提交或回滚。它还允许用户更轻松地指定事务的隔离级别。 - habib
2
我可能错了,但这里的回滚不是多余的吗?如果发生异常,事务将被处理并且更改将会丢失。 - Marie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接