11

I have my own repository that is as follows. However this does not take into account some of the new features such as the range features. Does anyone have a repository that includes everything. I have searched for this on the net but there's nothing that I can find that is recent. Here is what I have. I am hoping for something that has more and that offers IQueryable for many of the methods:

namespace Services.Repositories
{
    /// <summary>
    /// The EF-dependent, generic repository for data access
    /// </summary>
    /// <typeparam name="T">Type of entity for this Repository.</typeparam>
    public class GenericRepository<T> : IRepository<T> where T : class
    {
        public GenericRepository(DbContext dbContext)
        {
            if (dbContext == null) 
                throw new ArgumentNullException("An instance of DbContext is required to use this repository", "context");
            DbContext = dbContext;
            DbSet = DbContext.Set<T>();
        }

        protected DbContext DbContext { get; set; }

        protected DbSet<T> DbSet { get; set; }

        public virtual IQueryable<T> Find(Expression<Func<T, bool>> predicate)
        {
            return DbSet.Where<T>(predicate);
        }

        public virtual IQueryable<T> GetAll()
        {
            return DbSet;
        }

        public virtual T GetById(int id)
        {
            //return DbSet.FirstOrDefault(PredicateBuilder.GetByIdPredicate<T>(id));
            return DbSet.Find(id);
        }

        public virtual void Add(T entity)
        {
            DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
            if (dbEntityEntry.State != EntityState.Detached)
            {
                dbEntityEntry.State = EntityState.Added;
            }
            else
            {
                DbSet.Add(entity);
            }
        }

        public virtual void Update(T entity)
        {
            DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
            if (dbEntityEntry.State == EntityState.Detached)
            {
                DbSet.Attach(entity);
            }  
            dbEntityEntry.State = EntityState.Modified;
        }

        public virtual void Delete(T entity)
        {
            DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
            if (dbEntityEntry.State != EntityState.Deleted)
            {
                dbEntityEntry.State = EntityState.Deleted;
            }
            else
            {
                DbSet.Attach(entity);
                DbSet.Remove(entity);
            }
        }

        public virtual void Delete(int id)
        {
            var entity = GetById(id);
            if (entity == null) return; // not found; assume already deleted.
            Delete(entity);
        }
    }
}
Samantha J T Star
  • 30,952
  • 84
  • 245
  • 427
  • 4
    Please don't use a generic repository. It is a really bad pattern when already using an abstraction like Entity Framework. You will gain nothing from it, and it will actually hurt your ability to optimize later. Friends don't let friends use the Repository pattern. – Khalid Abuhakmeh Feb 13 '14 at 15:44

3 Answers3

23

You can add new features like this:

public virtual void AddRange(IEnumerable<T> entities)
{
    DbContext.Set<T>().AddRange(entities);
}

public virtual void RemoveRange(IEnumerable<T> entities)
{
    DbContext.Set<T>().RemoveRange(entities);
}
Colin
  • 22,328
  • 17
  • 103
  • 197
12

You don't need a generic repository. DbContext already is a generic repository. Try this out:

public class EntityDbContext : DbContext, IWriteEntities
{
    public IQueryable<TEntity> EagerLoad<TEntity>(IQueryable<TEntity> query,
        Expression<Func<TEntity, object>> expression)
    {
        // Include will eager load data into the query
        if (query != null && expression != null)
            query = query.Include(expression);
        return query;
    }

    public IQueryable<TEntity> Query<TEntity>()
    {
        // AsNoTracking returns entities that are not attached to the DbContext
        return Set<TEntity>().AsNoTracking();
    }

    public TEntity Get<TEntity>(object firstKeyValue, params object[] otherKeyValues)
    {
        if (firstKeyValue == null) throw new ArgumentNullException("firstKeyValue");
        var keyValues = new List<object> { firstKeyValue };
        if (otherKeyValues != null) keyValues.AddRange(otherKeyValues);
        return Set<TEntity>().Find(keyValues.ToArray());
    }

    public Task<TEntity> GetAsync<TEntity>(object firstKeyValue, params object[] otherKeyValues)
    {
        if (firstKeyValue == null) throw new ArgumentNullException("firstKeyValue");
        var keyValues = new List<object> { firstKeyValue };
        if (otherKeyValues != null) keyValues.AddRange(otherKeyValues);
        return Set<TEntity>().FindAsync(keyValues.ToArray());
    }

    public IQueryable<TEntity> Get<TEntity>()
    {
        return Set<TEntity>();
    }

    public void Create<TEntity>(TEntity entity)
    {
        if (Entry(entity).State == EntityState.Detached)
            Set<TEntity>().Add(entity);
    }

    public void Update<TEntity>(TEntity entity)
    {
        var entry = Entry(entity);
        entry.State = EntityState.Modified;
    }

    public void Delete<TEntity>(TEntity entity)
    {
        if (Entry(entity).State != EntityState.Deleted)
            Set<TEntity>().Remove(entity);
    }

    public void Reload<TEntity>(TEntity entity)
    {
        Entry(entity).Reload();
    }

    public Task ReloadAsync<TEntity>(TEntity entity)
    {
        return Entry(entity).ReloadAsync();
    }

    public void DiscardChanges()
    {
        foreach (var entry in ChangeTracker.Entries().Where(x => x != null))
        {
            switch (entry.State)
            {
                case EntityState.Added:
                    entry.State = EntityState.Detached;
                    break;
                case EntityState.Modified:
                    entry.State = EntityState.Unchanged;
                    break;
                case EntityState.Deleted:
                    entry.Reload();
                    break;
            }
        }
    }

    public Task DiscardChangesAsync()
    {
        var reloadTasks = new List<Task>();
        foreach (var entry in ChangeTracker.Entries().Where(x => x != null))
        {
            switch (entry.State)
            {
                case EntityState.Added:
                    entry.State = EntityState.Detached;
                    break;
                case EntityState.Modified:
                    entry.State = EntityState.Unchanged;
                    break;
                case EntityState.Deleted:
                    reloadTasks.Add(entry.ReloadAsync());
                    break;
            }
        }
        return Task.WhenAll(reloadTasks);
    }
}

... and the interfaces are merely a formality if you need to separate UoW from queries from commands:

public interface IUnitOfWork
{
    int SaveChanges();
    Task<int> SaveChangesAsync();
    Task DiscardChangesAsync();
    void DiscardChanges();
}

public interface IReadEntities
{
    IQueryable<TEntity> Query<TEntity>();

    IQueryable<TEntity> EagerLoad<TEntity>(IQueryable<TEntity> query, 
        Expression<Func<TEntity, object>> expression);
}

public interface IWriteEntities : IUnitOfWork, IReadEntities
{
    TEntity Get<TEntity>(object firstKeyValue, params object[] otherKeyValues);
    Task<TEntity> GetAsync<TEntity>(object firstKeyValue,
        params object[] otherKeyValues);
    IQueryable<TEntity> Get<TEntity>();
    void Create<TEntity>(TEntity entity);
    void Delete<TEntity>(TEntity entity);
    void Update<TEntity>(TEntity entity);
    void Reload<TEntity>(TEntity entity);
    Task ReloadAsync<TEntity>(TEntity entity);
}

With this, your interface doesn't need to be generic because the methods are generic.

private readonly IWriteEntities _entities;
...
_entities.Get<MyEntityA>(keyA);
await _entities.GetAsync<MyEntityB>(keyB);
_entities.Get<MyEntityC>.Where(...
var results = await _entities.Query<MyEntityD>().SingleOrDefaultAsync(...

etc. You just saved 3 unnecessary generic repository dependencies in the code above. One interface can handle all 4 of the entitiy types.

danludwig
  • 46,965
  • 25
  • 159
  • 237
  • Thanks for your suggestion. I think it's certainly something we should look into in the future. However right now we're already using a generic repository so we would like to bring this up to date as much as possible with features that are available in EF6.1 – Samantha J T Star Feb 06 '14 at 04:36
  • I dont think these sorts of comments are helpful. Granted, DbContext CAN function as a "generic repository" there are other reasons you would want to abstract this into your own. Namely the ability use generics and IoC to inject multiple contexts without having to couple your repository implementations to the context itself. – Brandon Mar 22 '14 at 12:29
  • @Brandon, your comment assumes that everyone else is aware that DbContext can be used as a generic repository by itself. I believe this answer is helpful because it assumes they are not. Also, I would argue that many, if not most applications do not use multiple contexts. When they must, I agree that decoupling the repository implementations from context is appropriate. Otherwise, it's my opinion that it's overkill. – danludwig Mar 23 '14 at 04:22
  • For the Query function above; the line: return Set().AsNoTracking(); shows : Error 2 The type 'TEntity' must be a reference type in order to use it as parameter 'TEntity' in the generic type or method 'System.Data.Entity.DbContext.Set()' ... Is there some reference required to quelle the savage beast? If I add 'where TEntity : Client' to the definition of the signature, it's fine. So do I need a base class for all DB object Types? – Robert Achmann Jun 13 '14 at 17:29
  • @RobertAchmann while I do recommend implementing the entity supertype pattern for all EF entity classes, did you try adding `where TEntity : class`? – danludwig Jun 13 '14 at 17:44
  • Ok - "If all else fails, read the instruction manual" seemed to be my motto today - @danludwig - you are correct, that seems to be the proper way – Robert Achmann Jun 13 '14 at 19:06
  • A generic repository wrapping EF isn't such a bad idea when you consider that there'll be potentially a different db tech in 10 years time. If you want your code to be able to last over then abstracting away from specific implementations minimises the rework – Stephen York Jun 14 '18 at 01:58
3

Take a look at Generic Unit of Work & Repositories Framework. You can download the entire repository project, modify the code to include the range functions, implement it in your own solution, etc.

Here's an example of its usage in the context of an OData/WebAPI controller method returning a DTO, rather than the EF entity.

            var results = odataQueryOptions.ApplyTo(_uow.Repository<ContentType>()
                .Query()
                .Get()
                .Include(u => u.User)
                .Where(u => u.UserId == userId)
                .OrderBy(o => o.Description)).Cast<ContentType>()
                .Select(x => new ContentTypeDTO()
                {
                    //projection goes here
                    ContentTypeId = x.ContentTypeId,
                    Description = x.Description,
                    UserDTO = new UserDTO 
                    { 
                        UserId = x.UserId,
                        UserName = x.User.UserName
                    }
                });

Hope that helps.

ElHaix
  • 12,846
  • 27
  • 115
  • 203