My team is in the process of updating a legacy project. We've decided to incorporate the Repository Pattern along with Entity Framework
in our Data Access layer. Below is a high-level view of this organization:
IRepository<TEntity>
is a generic interface used to perform common operations on an entity set:
public interface IRepository<TEntity> where TEntity : class
{
ObjectSet<TEntity> EntitySet { get; set; }
TEntity Get(object key);
void Insert(TEntity entity);
void Update(TEntity entity);
void Save();
}
For example if I have an Employee
entity, I can create the EmployeeRepository:IRepository<Employee>
object. The DataFactory
class is then used as a wrapper object to take care of ObjectContext
generation, disposal, and exception handling:
public class DataFactory<TContext, TEntity> : IDisposable
where TContext : ObjectContext, new()
where TEntity : class
{
private bool _disposed = false;
private TContext _context;
private IRepository<TEntity> _repository;
public DataFactory()
{
_context = new TContext();
IRepository<TEntity> repoistory;
bool isRepositoryFound = TryGetRepository(out repoistory);
if (!isRepositoryFound)
{
throw new InvalidOperationException(
string.Format("Unable to find repository of type {0}.", typeof(TEntity).FullName));
}
_repository = repoistory;
}
public DataFactory(TContext context, IRepository<TEntity> repository)
{
_context = context;
_repository = repository;
}
public void Do(Action<TContext, IRepository<TEntity>> action)
{
try
{
action(_context, _repository);
}
catch (Exception ex)
{
ProcessException(ex);
}
}
public TResult DoAndReturn<TResult>(Func<TContext, IRepository<TEntity>, TResult> action)
{
try
{
return action(_context, _repository);
}
catch (Exception ex)
{
ProcessException(ex);
return default(TResult);
}
}
private bool TryGetRepository(out IRepository<TEntity> repository)
{
Type repositoryType = Assembly.GetExecutingAssembly().GetTypes().SingleOrDefault(t =>
typeof(IRepository<TEntity>).IsAssignableFrom(t));
if (repositoryType == null)
{
repository = null;
return false;
}
repository = (IRepository<TEntity>)Activator.CreateInstance(repositoryType, _context );
return true;
}
}
And finally to use this class in the Business Layer, we would have something like:
using (var factory = new DataFactory<MyDataContext, Employee>())
{
factory.Do((context, repository) =>
{
// Interact with the repository
});
}
My coworker and I had a long conversation about whether the data context object should be exposed to the underlying layers in the Do()
and DoAndReturn()
methods. While I do see cases where having direct access to the context could be useful (for example in a case where we might want to turn on/off lazy loading for a particular entity set), I think doing so defeats the whole purpose of abstracting the Data Access layer (which is achieved by providing a common IRepository
contract) since now objects can be directly accessed/manipulated through the ObjectContext
.
He suggested having two different versions of these methods: one that exposes only the repository, and one that exposes both the context and repository. Is this an acceptable approach? Any suggestions are appreciated.