I am implementing the unit of work pattern to be used in the logic layer of my application. The goal is to abstract away the tasks of managing transactions / connections.
My implementation relies on generic repositories that exists during the life time of the unit of work class.
UnitOfWork
public class UnitOfWork : IUnitOfWork
{
private MySqlConnection connection;
private MySqlTransaction transaction;
private List<object> repos = new List<object>();
public UnitOfWork(string connectionStringId)
{
string connectionString = ConfigurationManager.ConnectionStrings[connectionStringId].ConnectionString;
connection = new MySqlConnection(connectionString);
connection.Open();
transaction = connection.BeginTransaction();
}
public IUnitOfWork Add<T>() where T : class {
IRepository<T> repo = new Repository<T>(connection, transaction);
repos.Add(repo);
return this;
}
public IRepository<T> Get<T>() where T : class
{
foreach (object repo in repos)
{
if (repo is IRepository<T>)
{
return (IRepository<T>) repo;
}
}
return null;
}
public void Dispose() {
try
{
transaction.Commit();
}
catch (InvalidOperationException e)
{
//Thrown if the transaction has already been rolled back.
}
catch (MySqlException e)
{
transaction.Rollback();
throw e;
}
transaction.Dispose();
connection.Close();
connection.Dispose();
}
}
Repository
public class Repository<T> : IRepository<T> where T : class {
private MySqlConnection connection;
private MySqlTransaction transaction;
public Repository(MySqlConnection connection, MySqlTransaction transaction) {
this.connection = connection;
this.transaction = transaction;
}
public long Create(T item) {
try {
return connection.Insert(item);
} catch (MySqlException e) {
transaction.Rollback();
throw e;
}
}
public bool Delete(T item) {
try {
return connection.Delete(item);
} catch (MySqlException e) {
transaction.Rollback();
throw e;
}
}
public bool Update(T item) {
try {
return connection.Delete(item);
} catch (MySqlException e) {
transaction.Rollback();
throw e;
}
}
public T Get(long id) {
return connection.Get<T>(id);
}
public IEnumerable<T> List() {
return connection.GetAll<T>();
}
}
Example Usage
using (IUnitOfWork UOW = new UnitOfWork("the_db")
.Add<A>()
.Add<B>())
{
IRepository<A> aRepo = UOW.Get<A>();
IRepository<B> bRepo = UOW.Get<B>();
aRepo.Create(new A( ... ) );
aRepo.Create(new A( ... ));
int aCount = aRepo.List().Count();
Console.WriteLine(aCount); // 2
long id = bRepo.Create(new B( ... ));
B b = bRepo.Get(id);
bRepo.Delete(b);
}
Test
#Test to see if the transaction is rolled back on unsuccessful create.
using (IUnitOfWork UOW = new UnitOfWork("the_db")
.Add<User>()
.Add<Schedule>())
{
UOW.Get<User>().Create(new User
{
Username = "test",
Email = "hello",
Hash = "lol"
});
try
{
UOW.Get<Schedule>().Create(new Schedule
{
Name = "Invalid",
UserId = 123
});
//Fails because UserId is a foreign key that references non existing user.
}
catch (MySqlException e)
{
Assert.AreEqual(0, UOW.Get<User>().List().Count());
return;
}
Assert.Fail();
}
I am thinking about how to enforce that the list of objects in UnitOfWork
are in fact repositories but haven't figured it out how yet.