Kieran Benton
Kieran Benton

Reputation: 8890

How can I share a 'unit of work' between multiple service methods?

I'm having an issue implementing the unit of work pattern in an application service that I'm building as part of a prototype. I think I'm either:

a) Missing something in the capabilities of autofac that I don't know it can do OR

b) Misusing the unit of work pattern entirely and need to refactor my service and/or repositories.

Essentially my problem stems from code sharing within my service. Specifically I have a service method called CreateCustomerAsync(…) and within that I build a unit of work (wrapping a db connection and beginning a db transaction) and use a repository to insert into a couple of database tables. That works fine until I want to call out from that method (and within the scope of the UOW) to another service method called AddCustomerToGroupAsync(…) in order to (within the same UOW) add a customer to a group (adding a row to a link table). AddCustomerToGroupAsync itself uses its own unit of work internally in order to make sure its repository actions happen within a DB transaction as well.

At the moment I cant do all that within the same UOW - in fact with the code like that it actually doesn’t work at all, since the inner most UOW is running over a different connection it can't see the customer that has been inserted in the outer transaction yet! I could re-order the code so that the AddCustomerToGroupAsync call is outside of the parent UOW but then I lose database integrity.

So - I roughly (this isnt syntactically correct - but representative of the problem I'm facing) something like this:

public async Task<int> CreateCustomerAsync(string name, int groupid)
{
    // do some validation etc..

    // NOTE: UnitOfWork and CustomerRepository are scoped to InstancePerMatchingLifetimeScope for 'tx'
    using(var scope = this.Container.BeginLifetimeScope("tx"))
    using(var uow = scope.Resolve<UnitOfWork>())
    {
        // NOTE: ResolveRepository is an extension method - the repo is having the uow injected into it
        var customerrepository = uow.ResolveRepository<CustomerRepository>();

        // multiple repository calls all within the same UOW/db transaction
        int newid = await customerrepository.CreateAsync(name);
        await customerrepository.ActivateAsync(newid);

        // here we invoke our seperate service method... and which I would *like* to execute within
        // this same UOW - so if it fails then all of the db statements executed so far get rolled back
        await this.AddCustomerToGroupAsync(newid, groupid);

        uow.Commit();
    }
}

public async Task<bool> AddCustomerToGroupAsync(int customerId, int groupId)
{
    // really here I'd LIKE to resolve the same lifetime scope that was constructed in the parent if it doesnt
    // exist with the tag specified already...
    // if i could do that then I would be able to resolve the *same* unit of work which would be a step forward
    using(var scope = this.Container.BeginLifetimeScope("tx"))
    using (var uow = scope.Resolve<UnitOfWork>())
    {
        var grouprepository = uow.ResolveRepository<GroupRepository>();

        // two repository calls that need to be wrapped in the same UOW/TX
        int linkid = await grouprepository.CreateLinkAsync(customerId, groupId);
        await grouprepository.ActivateAsync(linkid);

        uow.Commit();
    }
}

Any pointers to try and achieve this or is my approach fundamentally misguided?

Cheers.

Upvotes: 1

Views: 2428

Answers (1)

KnightFox
KnightFox

Reputation: 3252

Consider writing a private method for AddCustomerToGroupAsync that does bulk of the work.

   private async Task<bool> AddCustomerToGroupInternalAsync(int customerId, int groupId, UnitOfWork uow)
   { ../* All the code in the AddCustomerToGroupAsync inside the unitOfWork */. }

For the existing AddCustomerToGroupAsync method, you can open a scope, resolve a unitOfWork and pass it to AddCustomerToGroupInternalAsync method. Similarly for the existing CreateCustomerAsync method, you can pass the UnitOfWork resolved in that method to the AddCustomerToGroupInternalAsync method.

You can call uow.Commit after the call to AddCustomerToGroupInternalAsync in both cases.

Upvotes: 1

Related Questions