So I found this about prematurely commiting (source):
One issue to be aware of is that the UserStore class does not play well when using the unit of work design pattern. Specifically, the UserStore invokes SaveChanges in nearly every method call by default, which makes it easy to prematurely commit a unit of work. To change this behavior, change the AutoSaveChanges flag on the UserStore.
So the problem is that changing that flag makes working with the Identity objects harder. For example, when creating a new user you don't get back its ID.
Also how would I save the changes in a class like AccountController in which by default you don't even have (or often need) to declare an ApplicationDbContext and you only have a UserManager and a SignInManager? I'd need to instantiate a new ApplicationDbContext and save that, which isn't a very good solution.
I found a solution that consisted of using database transactions, so something like this:
var dbContext = // get instance of your ApplicationDbContext
var userManager = // get instance of your ApplicationUserManager
using (var transaction = dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted))
{
try
{
var user = // crate your ApplicationUser
var userCreateResult = await userManger.CreateAsync(user, password);
if(!userCreateResult.Succeeded)
{
// list of errors in userCreateResult.Errors
transaction.Rollback();
return userCreateResult.Errors;
}
// new Guid for user now saved to user.Id property
var userId = user.Id;
var addToRoleresult = await userManager.AddToRoleAsync(user.Id, "My Role Name");
if(!addToRoleresult.Succeeded)
{
// deal with errors
transaction.Rollback();
return addToRoleresult.Errors;
}
// if we got here, everything worked fine, commit transaction
transaction.Commit();
}
catch (Exception exception)
{
transaction.Rollback();
// log your exception
throw;
}
}
(source: https://stackoverflow.com/a/27367675/3194577)
But I'm confused about how this really fixes anything. Lets say you have an execution order like this:
- Add/Update something in the context, but don't save anything to the database
- Execute UserManager.CreateAsync which would save the context (which we don't want yet)
If I surround this with a transaction, it would still happen the same way, except I could rollback it if I'd want to, right? But that doesn't really help, it only helps if something does go wrong, and I never want anything to wrong in the first place which is why I want control over when it saves. Maybe I'm just confused about how transactions work.