Repository Pattern – Exposing Data Context to Underlying Layers

centity-frameworkrepository

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:

enter image description here

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.

Best Answer

Your setup screams one question: why did you introduce the abstractions? If you can't answer that (and I have the feeling you're not sure you can, given the nature of the question you posted) get rid of them. Entity framework and other ORMs form an abstraction of their own for database interaction, unit of work etc. Wrapping that in another set of abstraction classes which actually abstract the same thing is just creating more complexity. If I may guess, I think you created this abstraction do be able to order DB work without exposing DB oriented classes, so be able to store work through a repository without working with a context at the BL level. But in general that's just a fallacy: calling into DB oriented code through a wrapper isn't really abstracting anything.

What does help and what you didn't do, is creating classes for using the ORM with the business logic. A typical example of this is logic which makes sure different business logic methods participate in the same underlying database transaction or better: the work generated by the business logic is collected in the same unit of work.

To achieve that, it's a start to model the Business Logic actions into classes which can e.g. result in a series of commands. The BL won't have access to any DB oriented code, they just formulate what should be done. The action / command objects are then sent to class which does use DB oriented logic and which actually executes the commands / actions.

The repository pattern is 'nice' but IMHO too many times people get into trouble rather quickly as it creates a separate hierarchy of dependencies next to the entity dependencies themselves (e.g. Customer.Orders, with repositories you shouldn't be able to access that Orders collection, as they're part of the Orders repository) and isn't a real answer to what they're really struggling with.

Now it would have been great if EF had a separate unit of work class which would have been an easy solution for your problem, but alas... MS too followed Ambler's design of an ORM and ended up with a central session/context/unit-of-work hybrid.