3

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:

  1. Add/Update something in the context, but don't save anything to the database
  2. 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.

Community
  • 1
  • 1
Fabis
  • 1,932
  • 2
  • 20
  • 37
  • If you are using EF, why do you need Unit of Work? DbContext uses UoW internally, why do you need to re-implement it again? – trailmax May 11 '15 at 18:37
  • Because that's what every single guide on the web is telling me to do. I realized this a few hours back when I found this MS article about the changes in EF6 and that I don't need to do this anymore. Of course, people stopped writting guides after EF6, because I've spent 2 days on this and there's only articles about what to do before EF6 and I only found out about what you said a couple of hours ago. Anyway, how would I unit test without UoW? With a custom UoW I could inject anything I'd want, but I don't know how I'd do that with just DbContext. – Fabis May 11 '15 at 20:01
  • For unit testing I found the best approach is to send actual requests to a database using the real `DbContext`. Alternatively you can use repositories and provide mocks for them when you inject them, but I found this approach sub-optimal over time. – trailmax May 11 '15 at 20:03
  • Those are integration tests though, right? Which is why I want to use repositories, but then I also read that since EF6 I don't need repositories either since I can just use DbSets? – Fabis May 11 '15 at 20:16
  • Integration tests, yes. Repositories are good for updating, not for querying. Though I use less and less of them. Here is how we do integration tests: http://tech.trailmax.info/2014/03/how-we-do-database-integration-tests-with-entity-framework-migrations/ this guide is a year old and almost nothing is changed in our approach. – trailmax May 11 '15 at 20:35
  • I have to do unit tests though (this is a part of my university course) – Fabis May 11 '15 at 20:48
  • Should've started from that then) In that case wrap `DbContext` into repositories and you can mock them for testing. – trailmax May 11 '15 at 21:00
  • Can't I use DbSets instead of repositories though? – Fabis May 11 '15 at 22:04
  • `DbSets` are also not very test-friendly. Though you might try `IDbSets` and mock them for tests, though I've not tried that route, so can't comment. – trailmax May 11 '15 at 22:09
  • Can verify what @trailmax said. Repos with an injected and mocked dbcontext has worked the best for us in the past. No unit of work needed. – jamesSampica May 12 '15 at 03:00
  • Alright, well the original question about prematurely committing still stands though – Fabis May 12 '15 at 08:34

0 Answers0