13

I'm currently using Entity Framework with a Generic Repository and Unit Of Work Pattern. My Model is similar to the one described in this article

I've used Generic Repositories in the past and really enjoyed the global functionality it can provide. However, it seems I'm running into more problems every single day when it comes to using it with Entity Framework. These problems seem to arise even more when it comes to handling Parent/Children/Junction relationships.

Using a Generic Repository with EF is starting to leave a bad taste in my mouth, and I'm beginning to think that using a Generic Repository with EF is the wrong approach.

Could someone please help steer me in the right direction?

Lando
  • 2,288
  • 8
  • 33
  • 46
  • 2
    It depends on what your goal is of having a generic repository / UoW. If you consider `DbSet` to already be a generic repository, and `DbContext` (`SaveChanges`) being your UoW, then it could be your duplicating these concepts. It might make testing easier, at the expense of having another level of abstraction. I'm using that for my Repo / UoW, and for testing I'm using http://effort.codeplex.com – Matthew Jul 17 '13 at 20:56
  • My only real purpose to using a Generic Repository is to have Global, Generic CRUD Methods. – Lando Jul 17 '13 at 21:04
  • 1
    `DbSet<>` already accomplishes those. Testing on a generic repository is easier, and decouples the dependency on Entity Framework, if that matters. – Matthew Jul 17 '13 at 21:07
  • Performance and complexity are also things to consider. Data access through EF is measurably slower than ADO.net, and EF adds an extra layer of complexity to your application, ultimately giving you less control over your DAL. – kmdsax Jul 17 '13 at 21:08
  • @kmdsax I think the question is whether to use a Repository with EF, not whether to use EF at all. – Matthew Jul 17 '13 at 21:09
  • @Matthew Just posting my 2cents on his decision to use EF – kmdsax Jul 17 '13 at 21:14
  • @Lando see here for a recent set of opinions http://stackoverflow.com/questions/14110890/not-using-repository-pattern-use-the-orm-as-is-ef/17473500#17473500 – qujck Jul 17 '13 at 21:21
  • I wrote [this SO answer](http://stackoverflow.com/questions/17224015/entity-framework-4-1-generic-repository-set-up/17232382#17232382) to someone that was facing a similar issue. Might help. – MaxSC Jul 18 '13 at 07:49

1 Answers1

6

The approach of this article is really something that can be become a pain, because you already have a generic repository and a generic IUnitOfWork in EF and creating the specific repository for each type just removes the benefit of the generic!

I am posting here a sample of how i have a generic Repository and my IUnitOfWork, with this you can have a very nice repository!

public interface IUnitOfWork : IDisposable
{
    void Save();
    void Save(SaveOptions saveOptions);
}

public interface IRepository<TEntity> : IDisposable where TEntity : class
{
    IUnitOfWork Session { get; }
    IList<TEntity> GetAll();
    IList<TEntity> GetAll(Expression<Func<TEntity, bool>> predicate);
    bool Add(TEntity entity);
    bool Delete(TEntity entity);
    bool Update(TEntity entity);
    bool IsValid(TEntity entity);
}

And the implementation something like:

public class Repository : Component, IRepository
{

    protected DbContext session;

    public virtual IUnitOfWork Session
    {
        get
        {
            if (session == null)
                throw new InvalidOperationException("A session IUnitOfWork do repositório não está instanciada.");
            return (session as IUnitOfWork);
        }
    }

    public virtual DbContext Context
    {
        get
        {
            return session;
        }
    }

    public Repository(IUnitOfWork instance)
    {
        SetSession(instance);
    }

    public IList<TEntity> GetAll<TEntity>() where TEntity : class
    {
        return session.Set<TEntity>().ToList();
    }

    public IList<TEntity> GetAll<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
    {
        return session.Set<TEntity>().Where(predicate).ToList();
    }

    public bool Add<TEntity>(TEntity entity) where TEntity : class
    {
        if (!IsValid(entity))
            return false;
        try
        {
            session.Set(typeof(TEntity)).Add(entity);
            return session.Entry(entity).GetValidationResult().IsValid;
        }
        catch (Exception ex)
        {
            if (ex.InnerException != null)
                throw new Exception(ex.InnerException.Message, ex);
            throw new Exception(ex.Message, ex);
        }
    }

    public bool Delete<TEntity>(TEntity entity) where TEntity : class
    {
        if (!IsValid(entity))
            return false;
        try
        {
            session.Set(typeof(TEntity)).Remove(entity);
            return session.Entry(entity).GetValidationResult().IsValid;
        }
        catch (Exception ex)
        {
            if (ex.InnerException != null)
                throw new Exception(ex.InnerException.Message, ex);
            throw new Exception(ex.Message, ex);
        }
    }

    public bool Update<TEntity>(TEntity entity) where TEntity : class
    {
        if (!IsValid(entity))
            return false;
        try
        {
            session.Set(typeof(TEntity)).Attach(entity);
            session.Entry(entity).State = EntityState.Modified;
            return session.Entry(entity).GetValidationResult().IsValid;
        }
        catch (Exception ex)
        {
            if (ex.InnerException != null)
                throw new Exception(ex.InnerException.Message, ex);
            throw new Exception(ex.Message, ex);
        }
    }

    public virtual bool IsValid<TEntity>(TEntity value) where TEntity : class
    {
        if (value == null)
            throw new ArgumentNullException("A entidade não pode ser nula.");
        return true;
    }

    public void SetSession(IUnitOfWork session)
    {
        SetUnitOfWork(session);
    }

    protected internal void SetUnitOfWork(IUnitOfWork session)
    {
        if (!(session is DbContext))
            throw new ArgumentException("A instância IUnitOfWork deve um DbContext.");
        SetDbContext(session as DbContext);
    }

    protected internal void SetDbContext(DbContext session)
    {
        if (session == null)
            throw new ArgumentNullException("DbContext: instance");
        if (!(session is IUnitOfWork))
            throw new ArgumentException("A instância DbContext deve implementar a interface IUnitOfWork.");
        this.session = session;
    }

}
  • 1
    In the add what is the point of the try catch? Also, why not session.Set().Add(entity)? – Joel McBeth Oct 26 '13 at 00:18
  • The question was about general use, so keep im mind that this is just one sample to help to explain. This is not the point of the question. The point of the try catch is to be able to show the inner exception that is raised instead of a more generic error. Also using Set().Add(entity) would work in this case, but there are some point where we cannot use the generic version (e.g when you have inheritance and have to use the .TypeOf<>) – Gabriel Vonlanten C. Lopes Dec 23 '13 at 13:11
  • If you intended to rethrow the exception, then you can just say `throw;` as throwing a new exception will discard the current call stack. See http://stackoverflow.com/questions/881473/why-catch-and-rethrow-exception-in-c. I also don't agree with rethrowing the inner exception as anyone calling the method could just as easily check the inner exception too but with this code you aren't giving them the option. – Joel McBeth Dec 23 '13 at 20:07
  • 2
    You are missing the point. There is a Exception Framework implemented this framework that translates the exceptions to use friandly messages, this is why we throw the inner excpetion in here. Remeber we are making a wrapper for EF so this works under this context, the Wrapper takes care of the exceptions and throws. This would not be good in other contexts, but under this context this it correct. Be aware of the context, the throw here is just changing the mains message, the whole excpetion is there (no informationis lost), is just a matter of handlig the MESSAGE of the Excpetion. – Gabriel Vonlanten C. Lopes Dec 31 '13 at 15:02
  • In the context of this snippet what you are doing is not good exception handling. Just because this class wraps the entity framework doesn't mean you need to dumb down all the exceptions. By only throwing Exception you have made it impossible to catch any type of exception more specific than Exception. Also, wrapping a more specific exception in an Exception with it's inner Exception as the message is not a standard way of dealing with exceptions. Anyone trying to debug this code is going to be incredibly confused when looking at the what is thrown. – Joel McBeth Dec 31 '13 at 15:37
  • 1
    Unless you can actually handle the exception or you can frame it in a more meaningful way in your own custom exception you should just let the exception go and let the user of the interface deal with the exception how it is thrown. – Joel McBeth Dec 31 '13 at 15:41