1

We are starting to develop a small framework in our company, to share code between different applications. For data access we are using EF4. We have a custom DbContext class and a generic Repository:

public class RMDbContext : DbContext
{
    // ....
}

public interface IRepository 
{
    IQueryable<T> All();
    void Delete(T entity) where T : class;
    void Add(T entity) where T : class;
    void Update(T entity) where T : class;
    int SaveChanges();
    void RollbackChanges();
}

The problem here is how to implement the repository, using our custom DbContext class (RMDbContext). My co-worker thinks that the best way is to let RMDbContext implement the IRepository interface:

public class RMDbContext : DbContext, IRepository
{
    // ....
}

To be honest I don't like this approach, because the context is tied to a specific contract (IRepository). IMO it's better to create a repository implementation that uses the RMDbContext, something like this:

public class Repository<T> : IRepository where T : RMDbContext, new()
{
    protected readonly RMDbContext context;

    public class Repository()
    {
         context = new T();
    }

    // ....
}

What do you think about these 2 approaches? Which one would you choose, and why?

2
  • possible duplicate of Repository pattern with EF4 CTP5 Commented Nov 15, 2011 at 19:33
  • This is not a duplicate of that post. I have 2 scenarios and I want to know what are the pros and cons of each approach: 1) let the custom DbContext implement IRepository, or 2) create a separate class that implements IRepository interface and uses the custom DbContext object Commented Nov 21, 2011 at 23:20

4 Answers 4

4

personally i would encourage you guys to not create anything, just use the dbContext, it has all the methods you need anyways.

I myself implemented #1 (implementing IRepository), but you would end up doing some funky programming to get to the correct ObjectSet or EntitySet to add or delete from your Add/Delete methods.

That code would keep becoming more complex as you add inheritance hierarchies in your objectmodel.

Sign up to request clarification or add additional context in comments.

5 Comments

Just using DbContext is a bad idea because it can violate DRY. It's also hugely untestable.
i agree with testability, but curious how it violates DRY
Off the top of my head, operating only against currently logged-in users. The criteria may initially be Where (user => user.IsLoggedIn), but might move to Where (user => user.LastLogin <= DateTime.Now && user.LastLogout < user.LastLogin). With a Repository pattern, this is a one-line change.
@np-hard: I agree with insta, using just DbContext is a bad idea. I want a respository on top of EF, it's a layer of abstraction that hides the data access details. My question is: How should I implement the repository? Should I let the BaseDbContext implement it, or should I separate the context from the repository implementation? What are the pros and cons of each approach?
DbContext (and DbSet inside it) is an abstraction that hides the data access details. There's no need to abstract it further.
3

What we did at work was to implement a pattern like this:

interface ICRUD<T> : ICreatable<T>, IRetrievable<T>, IUpdatable<T>, IDeletable<T>
{
}

interface ICreatable<T>
{
    T Create();
}

interface IRetrieve<T>
{
    T Retrieve(params object[] keys);
}

interface IUpdatable<T>
{
    void Update(T existing);
}

interface ICreatable<T>
{
    void Delete(T existing);
}

And then we created an Entity-powered base repository:

public abstract class BaseRepository<TModel, TEntities> where TEntities : IDbSet<TModel>
{
    protected TEntities Entities {get; set;}
    protected DbContext Db {get; set;}

    public BaseRepository (DbContext db, TEntities entities)
    {
        Db = db;
        Entities = entities;
    }

    public virtual TModel Create() { return Entities.Create (); }
    public virtual TModel Retrieve (params object[] keys) { return Entities.Find (keys); }
    public virtual void Update (TModel existing) { Db.Entry(existing).State = Modified; }
    public virtual void Delete (TModel existing) { Db.Entry(existing).State = Removed; }
}

If you notice, the BaseRepository doesn't actually use ICRUD, just has identical method signatures. Since we code to interfaces, this lets us use a lot of shared code without exposing functionality we don't want with the base classes. A developer is free to implement the data store however they wish (ICRUD can talk to a webservice, for instance) with no knowledge of Entity, or they're also free to augment behavior provided by the BaseRepository by overriding any of the provided methods and doing something differently.

6 Comments

That's.... over-engineering, IMHO. No need to create a contract for every CRUD operation
How is it overengineered? We created four contracts, ICreatable being one, and thanks to generics in C# we can apply it to any of our domain models. It allows us to spin up a DI'ed, fully mockable repository in as few as 9 lines, including curly braces.
I just don't see the need to create 4 contracts, I would create only one: ` interface ICRUD<T> { T Create(); T Retrieve(params object[] keys); void Update(T existing); void Delete(T existing); } `
PS: Sorry for the formatting, I'm a newbie in stackoverflow. I was trying to format the source code but for some reason I wasn't able to do it
We use the repository pattern for all of our POCOs. There are some records, like audit records, that we don't want to expose delete functionality for. In that case, somebody would use an ICreatable<AuditRecord>. We do expose an actual ICRUD<T> interface for the majority case when all 4 operations are wanted, but ICRUD just pulls in the 4 base behaviors.
|
2

First you should not tightly coupled the repository with your RMDbContext because this is a bad design smell, you should always use interfaces.

Second the repository should not implement IRMDbContext interface if exist because the repository does not need it. You should use it and not implement it, so it's better if you create your Repository take a parameter of IRMDbContext in his constructor as the following

public class Repository {

protected readonly IRMDbContext context;

public class Repository(IRMDbContext rMDbContext)
{
    this.context = rMDbContext;
}

And the Unit of Work class the one that instantiate the context and send it to the repository see the following link

Unit Of Work With EF

Comments

2

EF Repositry best practice...
Entity Framework - Code First model approach, creates POCO entities for database table.
this model can be used either WCF data Contract or custom attributes.
this is my charming option using Dependency injection

IRepository Interface

/// <summary>
/// Repository
/// </summary>
public partial interface IRepository<T> where T : BaseEntity
{
    /// <summary>
    /// Returns the queryable entity set for the given type {T}.
    /// </summary>
    IQueryable<T> Table { get; }

    /// <summary>
    /// Creates a new instance of an entity of type {T}
    /// </summary>
    /// <returns>The new entity instance.</returns>
    T Create();

    /// <summary>
    /// Gets an entity by id from the database or the local change tracker.
    /// </summary>
    /// <param name="id">The id of the entity. This can also be a composite key.</param>
    /// <returns>The resolved entity</returns>
    T GetById(object id);

    /// <summary>
    /// Marks the entity instance to be saved to the store.
    /// </summary>
    /// <param name="entity">An entity instance that should be saved to the database.</param>
    /// <remarks>Implementors should delegate this to the current <see cref="IDbContext" /></remarks>
    void Insert(T entity);

    /// <summary>
    /// Marks multiple entities to be saved to the store.
    /// </summary>
    /// <param name="entities">The list of entity instances to be saved to the database</param>
    /// <param name="batchSize">The number of entities to insert before saving to the database (if <see cref="AutoCommitEnabled"/> is true)</param>
    void InsertRange(IEnumerable<T> entities, int batchSize = 100);

    /// <summary>
    /// Marks the changes of an existing entity to be saved to the store.
    /// </summary>
    /// <param name="entity">An instance that should be updated in the database.</param>
    /// <remarks>Implementors should delegate this to the current <see cref="IDbContext" /></remarks>
    void Update(T entity);

    /// <summary>
    /// Marks an existing entity to be deleted from the store.
    /// </summary>
    /// <param name="entity">An entity instance that should be deleted from the database.</param>
    /// <remarks>Implementors should delegate this to the current <see cref="IDbContext" /></remarks>
    void Delete(T entity);


    /// <summary>
    /// Returns the data context associated with the repository.
    /// </summary>
    /// <remarks>
    /// The context is likely shared among multiple repository types.
    /// So committing data or changing configuration also affects other repositories. 
    /// </remarks>
    IDbContext Context { get; }

    /// <summary>
    /// Gets or sets a value indicating whether database write operations
    /// such as insert, delete or update should be committed immediately.
    /// </summary>
    bool AutoCommitEnabled { get; set; }
}

Implementation

 /// <summary>
/// Entity Framework repository
/// </summary>
public partial class EfRepository<T> : IRepository<T> where T : BaseEntity
{

    #region Fields

    private readonly IDbContext _context;
    private IDbSet<T> _entities;

    #endregion

    #region Ctor

    public EfRepository(IDbContext context)
    {
        this._context = context;
        this.AutoCommitEnabled = true;
    }

    #endregion

    #region interface members

    public virtual IQueryable<T> Table
    {
        get
        {
            return this.Entities;
        }
    }

    public T Create()
    {
        return this.Entities.Create();
    }

    public T GetById(object id)
    {
        return this.Entities.Find(id);
    }

    public void Insert(T entity)
    {
        if (entity == null)
            throw new ArgumentNullException("entity");

        this.Entities.Add(entity);

        if (this.AutoCommitEnabled)
            _context.SaveChanges();
    }

    public void InsertRange(IEnumerable<T> entities, int batchSize = 100)
    {
        try
        {
            if (entities == null)
                throw new ArgumentNullException("entities");

            if (entities.HasItems())
            {
                if (batchSize <= 0)
                {
                    // insert all in one step
                    entities.Each(x => this.Entities.Add(x));
                    if (this.AutoCommitEnabled)
                        _context.SaveChanges();
                }
                else
                {
                    int i = 1;
                    bool saved = false;
                    foreach (var entity in entities)
                    {
                        this.Entities.Add(entity);
                        saved = false;
                        if (i % batchSize == 0)
                        {
                            if (this.AutoCommitEnabled)
                                _context.SaveChanges();
                            i = 0;
                            saved = true;
                        }
                        i++;
                    }

                    if (!saved)
                    {
                        if (this.AutoCommitEnabled)
                            _context.SaveChanges();
                    }
                }
            }
        }
        catch (DbEntityValidationException ex)
        {
            throw ex;
        }
    }

    public void Update(T entity)
    {
        if (entity == null)
            throw new ArgumentNullException("entity");

        if (this.AutoCommitEnabled)
        {
            _context.SaveChanges();
        }
        else
        {
            try
            {
                this.Entities.Attach(entity);
                InternalContext.Entry(entity).State = System.Data.EntityState.Modified;
            }
            finally { }
        }
    }

    public void Delete(T entity)
    {
        if (entity == null)
            throw new ArgumentNullException("entity");

        if (InternalContext.Entry(entity).State == System.Data.EntityState.Detached)
        {
            this.Entities.Attach(entity);
        }

        this.Entities.Remove(entity);

        if (this.AutoCommitEnabled)
            _context.SaveChanges();
    }


    public IDbContext Context
    {
        get { return _context; }
    }

    public bool AutoCommitEnabled { get; set; }

    #endregion

    #region Helpers

    protected internal ObjectContextBase InternalContext
    {
        get { return _context as ObjectContextBase; }
    }

    private DbSet<T> Entities
    {
        get
        {
            if (_entities == null)
            {
                _entities = _context.Set<T>();
            }
            return _entities as DbSet<T>;
        }
    }

    #endregion

}

using this we can add another repository as well implementation also in future. this will not affect any existing data. try it.....

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.