views:

384

answers:

1

I have a situation (I'm guessing is pretty standard) where I need to perform some business calculations and create a bunch of records in the database. If anything goes wrong at any point I need to roll everything back from the database. Obviosly I need some kind of transaction. My question is where do I implement transaction support. Here's my example

//BillingServices - This is my billing service layer. called from the UI
public Result GenerateBill(BillData obj)
{
     //Validate BillData

     //Create a receivable line item in the receivables ledger 
     BillingRepository.Save(receivableItem);

     //Update account record to reflect new billing information
     BillingRepository.Save(accountRecord);

     //...do a some other stuff
     BillingRepository.Save(moreStuffInTheDatabase);
}

If any of the updates to the database fail I need to roll the other ones back and get out. Do I just expose a Connection object through my Repository in which I can call

Connection.BeginTransaction()

or do I do I just validate in the service layer and just call one method in the repository that saves all the objects and handles the transaction? This doesn't quite seem right to me. It's seem like it would force me to put to much business logic in the data layer.

What's the right approach? What if I need to span repositories (or would that be bad design)?

+2  A: 

I'm assuming that you are using .NET here. That being the case, you can simply wrap the entire code section in a using statement with a TransactionScope instance and it will handle the transaction semantics for you. You simply have to call Complete at the end:

//BillingServices - This is my billing service layer. called from the UI
public Result GenerateBill(BillData obj)
{
     // Create the transaction scope, this defaults to Required.
     using (TransactionScope txScope = new TransactionScope())
     {
          //Validate BillData

          //Create a receivable line item in the receivables ledger 
          BillingRepository.Save(receivableItem);

          //Update account record to reflect new billing information
          BillingRepository.Save(accountRecord);

          //...do a some other stuff
          BillingRepository.Save(moreStuffInTheDatabase);

          // Commit the transaction.
          txScope.Complete();
     }
}

If an exception occurs, this has the effect of not calling Complete when the code block is exited and the transaction aborts, rolling everything back.

You can then nest this within other TransactionScope instances to create larger transactions across multiple repositories.

casperOne
So is what I'm doing pretty standard, and is this the solution that is most common?
Micah
@Micah: Assuming you are using .NET, yes, this is the preferred method when you want to span a transaction across multiple methods/objects/transactional repositories.
casperOne
Awesome! Thanks a lot!
Micah
Is this ok to do even with simple things like PersonRepository.SavePerson(person)PersonRepository.SaveAddress(address)Are there any concerns with using the transactionscope in simple situations like these?
Micah
According to NHProf, everything should be wrapped in an explicit transaction, even read operations. See here: http://nhprof.com/Learn/Alerts/DoNotUseImplicitTransactions
Daniel T.