In a project I'm working on, the data access layer has evolved to something that somewhat resembles the Unit Of Work and Repository patterns, at least they started with those names, I'm not entirely sure with how far they've drifted, if they can still be called that.
For classes data access, an implementation IWorkUnitFactory
is passed as a constructor parameter from the IoC container. This is a rather simple interface:
public interface IWorkUnitFactory
{
IWorkUnit Begin();
}
The IWorkUnit
interface it returns looks like this:
public interface IWorkUnit : IDisposable
{
T Insert<T>(T entity) where T : class;
T Load<T>(int id) where T : class;
void Delete<T>(int id) where T : class;
void Delete<T>(T entity) where T : class;
T Update<T>(T entity) where T : class;
T InsertOrUpdate<T>(T entity) where T : class;
IQueryable<T> Query<T>() where T : class;
IEnumerable<T> Execute<T>(string queryName, IDictionary<string, object> parameters);
void Commit();
void Rollback();
}
My IWorkUnitFactory
implementation for NHibernate basically just creates an NHibernate session, and passes it to the NHibernate implementation of IWorkUnit
, which is just a rather thin wrapper around the NHibernate session, which starts a transaction in its constructor, and rolls it back in the Dispose
implementation unless the Commit
method is called.
Then, anywhere data access happens, it follows this pattern:
using(var workUnit = WorkUnitFactory.Begin())
{
//Boring data access code here
workUnit.Commit();
}
Next up are the Repository
classes. These don't have any common inheritance or interface implementations, they just follow a few conventions, the main one being taking an IWorkUnit
in its constructor. These classes contain the interaction with the IWorkUnit
for operations like GetById
, Find
, Insert
, Update
, etc, and are responsible for small bits of logic (the Update
method updates the LastUpdated
field on the entity, Find
determines what fields to search based on its parameters, etc).
So, for example, a Repository for the User entity might look something like this:
public class UserRepository
{
private readonly IWorkUnit workUnit;
public UserRepository(IWorkUnit workUnit)
{
this.workUnit = workUnit;
}
public virtual User GetByEmail(string email)
{
return workUnit.Query<User>().FirstOrDefault(u => u.Email == email);
}
public virtual User Update(User user, User updater)
{
user.ModifiedBy = updater;
user.Modified = DateTimeOffset.Now;
return workUnit.Update(user);
}
}
Then, code to update a user would look something like this:
using(var workUnit = WorkUnitFactory.Begin())
{
var userRepo = new UserRepository(workUnit);
var userToEdit = userRepo.GetByEmail("[email protected]");
//Do some stuff to userToEdit here
userRepo.Update(userToEdit, currentRequestUser); //currentRequestUser is just whoever is performing the action
workUnit.Commit();
}
Any feedback on better naming for these things, or better methods? This has served me rather well, with the only thing I don't really like is creating the repository instances like that, But I can't think of a way to handle transactions and keep the somewhat lower level data access code away from higher level business logic.
1 Answer 1
If all your business classes (e.g., User) could inherit from a common class or interface that has Modified and ModifiedBy properties, you could make your Repository a generic Repository<T>. Class-specific methods like GetByEmail would be replaced with generalized methods that take Expressions. Would that address your concern about "creating the repository instances like that", while keeping the business-level concerns (e.g., the fact that you're retrieving by email and not something else) away from your data access code?
Here's an untested and even uncompiled example of what I'm talking about.
public interface IBusinessEntity
{
User ModifiedBy { get; set; }
DateTime Modified { get; set; }
}
public class Repository<T> where T : IBusinessEntity
{
private readonly IWorkUnit workUnit;
public Repository(IWorkUnit workUnit)
{
this.workUnit = workUnit;
}
public virtual T Get(Expression<Func<T,bool>> expression)
{
return workUnit.Query<T>().FirstOrDefault(expression);
}
public virtual T Update(T objToUpdate, User updater)
{
objToUpdater.ModifiedBy = updater;
objToUpdate.Modified = DateTimeOffset.Now;
return workUnit.Update(objToUpdate);
}
}
As for your naming conventions, they make sense to me.
Just one more idea: Unless your application will always be used in just one timezone, and hosted in the same timezone, you might consider storing your Modified property as UTC.
-
\$\begingroup\$ Will it work in SOA environment, for example like an Enteprise Domain Repository service? \$\endgroup\$onof– onof2012年09月07日 07:41:03 +00:00Commented Sep 7, 2012 at 7:41
-
\$\begingroup\$ It's very hard to mock LINQ Expressions. If you are going to unit test your business layer, you may want to avoid them. \$\endgroup\$Akira Yamamoto– Akira Yamamoto2014年10月14日 18:04:10 +00:00Commented Oct 14, 2014 at 18:04