Search code examples
asp.net.netasp.net-identity

Transactions with ASP.NET Core 1.0 Identity UserManager


In one of my apps, I'm rewriting Asp Core Identity UserManager's CreateAsync, to in addition to creating new user in UserStore - create a new row in a separate statistics table (on a different dbcontext). Problem is, I would like both of those operations to be fired inside a transaction, so that if one fails - the other does not commit. Here's the code :

public override async Task<IdentityResult> CreateAsync(TUser user, string password)
    {
        // We need to use transactions here.            
            var result = await base.CreateAsync(user, password);

            if (result.Succeeded)
            {
                var appUser = user as IdentityUser;

                if (appUser != null)
                {
                    // create user stats.
                    var userStats = new UserStats()
                    {
                        ActionsCount = 0,
                        EventsCount = 0,
                        FollowersCount = 0,
                        ProjectsCount = 0,
                        UserId = appUser.Id
                    };

                    _ctx.UserStats.Add(userStats);
                    await _ctx.SaveChangesAsync();
                }
            }    
            return result;
    }

Thing is, I have no idea how to set up such a transaction, as it seems TransactionScope is not a part of .Net Core yet (?) and getting currently scoped DbContext for base.CreateAsync() with HttpContext.GetOwinContext() does not work as HttpContext seems to be missing this method (referencing Microsoft.Owin.Host.SystemWeb or Microsoft.AspNet.WebApi.Owin as hinted in some other stack overflow answers won't do - both are not compatible with .netcore). Any help ?


Solution

  • Firstly you need to setup the dbcontext you are using with identity with a scoped lifetime:

            services.AddDbContext<MyDbContext>(ServiceLifetime.Scoped); // this is the important bit
    
            services.AddIdentity<User, Role>(options =>
            {
            })
            .AddEntityFrameworkStores<MyDbContext, int>()
            .AddDefaultTokenProviders();
    

    Then when you need to create a transaction you do the following:

            using (var transaction = await _myDbContext.Database.BeginTransactionAsync())
            {
                var result = await _userManager.CreateAsync(newUser, password);
    
                try
                {
                    if (result.Succeeded)
                    {
                        DBItem newItem = new DBItem();
                        _myDbContext.Add(newItem);
    
                        await _myDbContext.SaveChangesAsync();
                        transaction.Commit();
                    }
                }
                catch (Exception e)
                {
                    transaction.Rollback();
                }
            }