Reputation:
What should a UoW be actually handled?
The way I see it, the UoW should not be handling commits and rollbacks. It should just provide means to do so, and should only be responsible with tracking changes on objects that are about to be committed such that if they are in some inconsistent state or something has changed, the transaction should fail?
So the way I see a UoW is something like this.
public interface IUnitOfWork : IDisposable
{
IAtomicTransaction BeginTransaction();
Task<object> SaveAsync<TEntity>(TEntity entity);
Task UpdateAsync<TEntity>(TEntity entity);
Task RemoveAsync<TEntity>(TEntity entity);
}
Committing should be something like this:
interface IAtomicTransaction : IDisposable
{
void Commit();
void Rolleback();
}
Take for example this post (and not only this but there are many like it),
Unit of Work + Repository Pattern in MVC5 and Web API 2 with Fluent NHibernate and Ninject
If you look you will see that it uses ISession within the repository, which I find it to be a mistake since it will directly link the Repository (your business) to NHibernate's ISession. Shouldn't the UoW take this responsibility? Should you start changing your business implementation because you change the framework? Shouldn't the UoW act as an Adapter, something like an anti-corruption layer?
Upvotes: 1
Views: 955
Reputation: 16409
As now you have edited your question, I have to change the way I answer; hence the second answer. My first answer is still valuable (hopefully ;)).
UoW should automatically figure out what (if any) changes needs to be flushed.
IAtomicTransaction BeginTransaction();
void Commit();
void Rolleback();
The above methods may or may not be the part of UoW. Many implementations of UoW expose those. The drawback of exposing those is that, transaction handling becomes responsibility of the caller; not your class. The plus point is that, the caller gets better control over the process.
If you want to bypass the drawback, see the alternative in my first answer.
Task<object> SaveAsync<TEntity>(TEntity entity);
Task UpdateAsync<TEntity>(TEntity entity);
Task RemoveAsync<TEntity>(TEntity entity);
The above methods are part of Repositories. Those cannot be the part of UoW. UoW should automatically figure out what to do based on change tracking. If we limit our discussion with just database transaction, then DbTransaction
handles this correctly. For more detailed ways to handle change tracking, please refer to my first answer.
Following is the NHibernate-based implementation. Please note that this is not recommended. If you are using a full ORM, you should avoid these type of implementations as they add little value to your design. If you simply replace ISession
with IDbConnection
, this could be implemented with ADO.NET as well
public interface IUnitOfWork : IDisposable
{
void Flush();
}
public sealed class UnitOfWork : IUnitOfWork
{
public UnitOfWork()
{
session = SessionFactroyHelper.CreateSession();
transaction = session.BeginTransaction();
}
ISession session = null;
ITransaction transaction = null;
void IUoWSession.Flush()
{
transaction.Commit();
}
void IDisposable.Dispose()
{
transaction.Dispose();
transaction = null;
session.Dispose();
session.Dispose();
}
}
By the way, this topic itself is opinion-based. How to implement UoW and Repositories is an individual design decision. If you are really keen about implementing correct(?) UoW, consider using some advanced ORM directly in the code bypassing the UoW wrapper.
Upvotes: 0
Reputation: 16409
UoW is more than just a transaction. Following is the quote from Martin Fowler:
A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done, it figures out everything that needs to be done to alter the database as a result of your work.
It should:
internal
constructor and some factory-like method in UoW creates repositories.interface IUnitOfWork : IDisposable
{
IDbConnection Connection { get; }
IDbTransaction Transaction { get; }
void Begin();
void Commit();
void Rollback();
IRepository CreateRepository(....);
}
Method CreateRepository
creates an instance of repository under this UoW. This way, you may share same UoW across multiple repositories. This way, one DB transaction can be spread across multiple repositories. Other alternative is to inject UoW in repository as shown here.
The problem with this approach is that, it does not force UoW. It is up to caller when (or whether) to start transaction.
Other mini-version of UoW (that forces the UoW) I can imagine is something like below:
public sealed class UoWSession
{
public UoWSession()
{
// Open connection here
// Begin transaction here
}
IRepository CreateRepository(....)
{
// Create and return the requested repository instance here
}
void Commit()
{
transaction.Commit();
}
void Dispose()
{
// If transaction is not committed, rollback it here.
// Clean up resources here.
}
}
Without using an ORM, you have to expose something that tells you that everything was fine. Above implementation uses Commit
method.
There may be a simple property, let’s say IsAllWell
, which is false
by default and calling code set it explicitly. Your Dispose
method then commits or rolls back the transaction based on property. In this case, you do not need to expose Commit
method as you are handling it internally on the flag.
Upvotes: 0