I'm working on a Web API project (ninject, raw sql queries, repository pattern, UoW pattern) I have checked almost everywhere for an article that would describe implementation of UoW with simple database transactions w/o using entity framework (plain SQL requests and SqlConnection etc...), but couldn't find anything.
The problem I have is the following. I have a Web API which has controllers that work with repositories who, in turn, work with DB through DBManager class injected into them through UoW.
Let us imagine I have 2 methods in a repository, each of them update data in a database:
Method1 - update ticket (add a post from customer). Method2 - update ticket's state (only if post was successful).
These methods may be called one after another, or separately i.e. Method2 may be called from some other method, after ticket was closed, for example.
Method1, before updating the DB creates a transaction through the DBManager. It then updates DB and calls Method2 to do his thing. Method2, since it can also be called as a standalone method, also starts the transaction before updating the DB. When query is executed it commits the transaction and returns to Method1. Method1 at this stage also commits the transaction as there were no exceptions and it wants to persist changes it made to the DB. But, it can't because changes were already committed by the Method2.
So the graph of actions is something similar to one below:
Method1()
DBManager.BeginTransaction() - begins new transaction
update DB - adds post to the ticket
Method2() - calls method 2 to update ticket status
DBManager.BeginTransaction() - returns transaction started by Method1()
update DB - updates ticket status
DBManager.CommitTransaction() - commits transaction
return
DBManager.CommitTransaction() - commits transaction to save ALL changes but can't since transaction was already committed.
If I need to call some other method after ticket status was updated, then method would work with completely new set of data since changes were committed into the DB by Method2().
I started thinking on how to fix this, but couldn't find anything. I have read about TransactionScope and thought I can do something like this:
public class UnitOfWork : IUnitOfWork, IDisposable
{
/// <summary>
/// DB context.
/// </summary>
private IDBManager _dbManager;
/// <summary>
/// Repository provider class which can create repositories on demand.
/// </summary>
private IRepositoryProvider _repositoryProvider;
private TransactionScope _transaction;
public UnitOfWork(IDBManager dbManager, IRepositoryProvider repoProvider)
{
_dbManager = dbManager;
_repositoryProvider = repoProvider;
}
public T GetRepository<T>()
{
if (_transaction == null)
_transaction = new TransactionScope();
return _repositoryProvider.Create<T>(_dbManager);
}
public void Save()
{
_transaction.Complete();
}
public void Dispose()
{
_transaction.Dispose();
}
}
In this case the TransactionScope would begin when I create my first repository and then I could call save in my controller, like so:
public TicketPost AddTicketPost(int tid, TicketPost update)
{
TicketPost post = Uow.GetRepository<ITicketsRepository>().AddPost(tid, update);
Uow.Save();
return post;
}
But, this would mean that TransactionScope would be created for any operation - select/update/delete, and it would last from the moment of creation of first repository (even when I might not need it) till the moment when the transaction is either disposed or completed.
Another solution would be to work with DBManager's transaction and call BeginTransaction from the controller and either Commit or Rollback when I need it. Like so:
Uow.BeginTransaction();
try
{
TicketPost post = Uow.GetRepository<ITicketsRepository>().AddPost(tid, update);
}
catch (Exception e)
{
Uow.RollbacTransaction();
}
Uow.CommitTransaction();
But I don't really like this method. I would need to catch exceptions when in first case those would be bubbled up to my ExceptionsHandler which would create response message to a client. Also, I think of a controler as a middle man that gets the request and says "hey, repository, here is the data, I have checked it, do your thing and call me back.". When it gets a "call" back from the repository it may do something else not work-with-data related, like send out e-mails. I like when controller doesn't need to call methods one by one in the same repository and think of things it needs to do to complete the job like:
- Update the ticket
- Set status
- Dome something else with the ticket
- Send out e-mails
instead of this, controller asks repository to take care of ticket update and waits while it can send out e-mails:
- Tell controller to do whatever he needs to do to update the ticket.
- Wait and send out e-mails.
I may be wrong in how I tread controllers and repositories. Please correct me if I'm wrong.
I hope someone could point me to a resource or, may be someone has similar setup and already found a solution for this case (transactions problem).
Any help would be appreciated. Thank you very much in advance.