6

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.

  • I've wondered about these things as well. I've heard people criticize the generic repository pattern when exposed to the rest of the application. It seems that some think the GR pattern should only be an implementation convenience when developing non-generic concrete repositories. I've occasionally felt that way too. I've also seen people recommend avoiding the Repository pattern altogether when using Entity Framework. My only concern there is duplication of queries, but that can be solved without resorting to classic Repositories. – MetaFight Sep 5 '14 at 17:51
4

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.

  • The reason we introduced another abstraction layer is because this is an old project that doesn't use EF (and we can't quite change that, not yet at least). Now we're adding new projects that will use EF but as far as the business layer goes, there shouldn't be any difference between these projects. In order to preserve consistency, we have to either make new projects "look" the same as the old one (which is exactly what we're trying to achieve) or introduce an intermediate layer to hide these inconsistencies. – PoweredByOrange Sep 8 '14 at 14:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.