4
\$\begingroup\$

User and Role are just examples; also, code is sparse on purpose (i.e. for demonstration purposes only).

Thoughts? (e.g. good, bad, etc.)

Interfaces

public interface IEntity
{
    int Id { get; }
}

public interface IUser
    : IEntity
{
    string Username { get; }
    string Email { get; }
    ICollection<IRole> Roles { get; }
}

public interface IRole
    : IEntity
{
    string Name { get; }
    ICollection<IUser> Users { get; }
}

public interface IContext<TEntity>
    : IDisposable
    where TEntity : class, IEntity
{
    IDbSet<TEntity> Entities();
    int SaveChanges();
}

public interface IUserContext<TUser>
    : IContext<TUser>
    where TUser : class, IUser
{
}

public interface IRoleContext<TRole>
    : IContext<TRole>
    where TRole : class, IRole
{
}

public interface IRepository<TEntity>
    : IDisposable
    where TEntity : class, IEntity
{
    TEntity Find(int id);
    IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> filter);
    void Add(TEntity entity);
    void Remove(TEntity entity);
}

public interface IUserRepository<TUser>
    : IRepository<TUser>
    where TUser : class, IUser
{
}

public interface IRoleRepository<TRole>
    : IRepository<TRole>
    where TRole : class, IRole
{
}

public interface IUnitOfWork<TUser, TRole>
    : IDisposable
    where TUser : class, IUser
    where TRole : class, IRole
{
    IUserRepository<TUser> Users();
    IRoleRepository<TRole> Roles();
    int SaveChanges();
}

Classes

public abstract class Entity
    : IEntity
{
    public virtual int Id { get; set; }
}

public class User
    : Entity, IUser
{
    private ICollection<IRole> _roles;

    public string Username { get; set; }
    public string Email { get; set; }

    public virtual ICollection<IRole> Roles
    {
        get { return _roles ?? (_roles = new Collection<IRole>()); }
        set { _roles = value; }
    }
}

public class Role
    : Entity, IRole
{
    private ICollection<IUser> _users;

    public string Name { get; set; }

    public virtual ICollection<IUser> Users
    {
        get { return _users ?? (_users = new Collection<IUser>()); }
        set { _users = value; }
    }
}

public abstract class Context<TContext, TEntity>
    : DbContext, IContext<TEntity>
    where TContext : DbContext
    where TEntity : class, IEntity
{
    static Context()
    {
        Database.SetInitializer<TContext>(null);
    }

    protected Context()
        : this("name=DefaultConnection")
    {
    }

    protected Context(string nameOrConnectionString)
        : base(nameOrConnectionString)
    {
    }

    public virtual IDbSet<TEntity> Entities()
    {
        return Set<TEntity>();
    }
}

public class UserContext<TUser>
    : Context<UserContext<TUser>, TUser>, IUserContext<TUser>
    where TUser : class, IUser
{
    public UserContext()
    {
    }

    public UserContext(string nameOrConnectionString)
        : base(nameOrConnectionString)
    {
    }
}

public class RoleContext<TRole>
    : Context<RoleContext<TRole>, TRole>, IRoleContext<TRole>
    where TRole : class, IRole
{
    public RoleContext()
    {
    }

    public RoleContext(string nameOrConnectionString)
        : base(nameOrConnectionString)
    {
    }
}

public abstract class Repository<TEntity>
    : IRepository<TEntity>
    where TEntity : class, IEntity
{
    private readonly IContext<TEntity> _context; 
    private bool _disposed;

    protected Repository(IContext<TEntity> context)
    {
        _context = context;
        _disposed = false;
    }

    public virtual TEntity Find(int id)
    {
        return _context.Entities().Find(id);
    }

    public virtual IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> filter = null)
    {
        return filter != null
            ? _context.Entities().Where(filter)
            : _context.Entities();
    }

    public virtual void Add(TEntity entity)
    {
        _context.Entities().Add(entity);
    }

    public virtual void Remove(TEntity entity)
    {
        _context.Entities().Remove(entity);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _context.Dispose();
            }

            _disposed = true;
        }
    }

    ~Repository()
    {
        Dispose(false);
    }
}

public class UserRepository<TUser>
    : Repository<TUser>, IUserRepository<TUser>
    where TUser : class, IUser
{
    public UserRepository()
        : this(new UserContext<TUser>())
    {
    }

    public UserRepository(IUserContext<TUser> context)
        : base(context)
    {
    }
}

public class RoleRepository<TRole>
    : Repository<TRole>, IRoleRepository<TRole>
    where TRole : class, IRole
{
    public RoleRepository()
        : this(new RoleContext<TRole>())
    {
    }

    public RoleRepository(IRoleContext<TRole> context)
        : base(context)
    {
    }
}

public class UnitOfWork<TUser, TRole>
    : IUnitOfWork<TUser, TRole>
    where TUser : class, IUser
    where TRole : class, IRole
{
    private readonly IUserContext<TUser> _userContext;
    private readonly IRoleContext<TRole> _roleContext;
    private readonly IUserRepository<TUser> _userRepository;
    private readonly IRoleRepository<TRole> _roleRepository;
    private bool _disposed;

    public UnitOfWork()
        : this(new UserContext<TUser>(), new RoleContext<TRole>())
    { 
    }

    public UnitOfWork(IUserContext<TUser> userContext, IRoleContext<TRole> roleContext)
    {
        _userContext = userContext;
        _roleContext = roleContext;
        _userRepository = new UserRepository<TUser>(_userContext);
        _roleRepository = new RoleRepository<TRole>(_roleContext);
        _disposed = false;
    }

    public IUserRepository<TUser> Users()
    {
        return _userRepository;
    }

    public IRoleRepository<TRole> Roles()
    {
        return _roleRepository;
    }

    public int SaveChanges()
    {
        return _userContext.SaveChanges() + _roleContext.SaveChanges();
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _userContext.Dispose();
                _roleContext.Dispose();
                _userRepository.Dispose();
                _roleRepository.Dispose();
            }

            _disposed = true;
        }
    }

    ~UnitOfWork()
    {
        Dispose(false);
    }
}
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

You should not dispose the context in the Repository's dispose method, because it has been injected via the constructor and you have no information if you can safely dispose it or not. If the same context has been passed into another repository then you would break it by disposing it.

Please read this for a full explanation and an example how to do it the correct way: https://dusted.codes/dont-dispose-externally-created-dependencies

In short you can re-factor the code to use a factory, so that you are in charge of creating and disposing a context object.

\$\endgroup\$
1
\$\begingroup\$

This seems about right.

There is only one thing I'd like to point out.

I'd change this:

public interface IEntity {
    int Id { get; }
}

public class Entity : IEntity {
    public virtual int Id { get; set; }
}

All your entities are assumed to have only one Id, ruling out every composite key.

Let's think about this common scenario:

User.cs

public class User {
    public int Id { get; set; }
    public string Username { get; set; }
}

Email.cs

public class Email {
    public int Id { get; set; }
    public string Address { get; set; }
}

UserEmail.cs

public class UserEmail {
    public int UserId { get; set; }
    public int EmailId { get; set; }
    public bool IsPrimary { get; set; } //Adding this prop requires to map this entity.
}

Given your base entity and entity interface you won't be able to handle composite keys. Also, keep in mind that Entity Framework will handle by itself the many to many relationships as long as the table contains no more than the Id's, when you add any other property you will have to add that entity to your project.

My advice is to move the Id to every entity, if you need/want to keep your base entity interface and class as a constraint, you could, but you dont really need the interface and the class.

Like:

public interface IEntity {
    int Id { get; }
}

public class Entity : IEntity {
    public virtual int Id { get; set; }
}

public class User : Entity, IUser {
    public int Id { get; set; }
    // ...
}

EDIT:

IProvider.cs

internal interface IProvider {
    MyContext DbContext { get; set; }

    IRepository<T> GetGenericRepository<T>()
        where T : class;

    T GetCustomRepository<T>(Func<MyContext, object> factory = null)
        where T : class;
}

Provider.cs

internal class Provider : IProvider {
    private readonly Factory _factory;
    protected Dictionary<Type, object> Repositories { get; private set; }
    public MyContext DbContext { get; set; }

    internal Provider() {
        _factory = new Factory();
        Repositories = new Dictionary<Type, object>(); }

    protected virtual T MakeRepository<T>(
        Func<MyContext, object> factory, MyContext dbContext) {
        var f = factory ?? _factory.GetRepositoryFactory<T>();
        if (f == null) throw new NotSupportedException(typeof(T).FullName);
        var repo = (T)f(dbContext);
        Repositories[typeof(T)] = repo;
        return repo; }

    public IRepository<T> GetGenericRepository<T>() where T : class {
        return GetCustomRepository<IRepository<T>>(
            _factory.GetRepositoryFactoryForEntityType<T>()); }

    public virtual T GetCustomRepository<T>(Func<MyContext, object> factory = null)
        where T : class {
        object repoObj;
        Repositories.TryGetValue(typeof(T), out repoObj);
        if (repoObj != null) { return (T)repoObj; }
        return MakeRepository<T>(factory, DbContext); }

    public void SetRepository<T>(T repository) {
        Repositories[typeof(T)] = repository; }
}

Factory.cs

internal class Factory {
    private readonly IDictionary<Type, Func<MyContext, object>> _factories;

    public Factory() { _factories = GetFactories(); }

    public Factory(IDictionary<Type, Func<MyContext, object>> factories) {
        _factories = factories; }

    private IDictionary<Type, Func<MyContext, object>> GetFactories() {
        return new Dictionary<Type, Func<MyContext, object>>
            { { typeof(ILogRepository),
                    context => new LogRepository(context) },
                { typeof(IExtendedRepository),
                    context => new ExtendedRepository(context) } }; }

    protected virtual Func<MyContext, object> DefaultEntityRepositoryFactory<T>()
        where T : class {
        return dbContext => new Repository<T>(dbContext); }

    public Func<MyContext, object> GetRepositoryFactory<T>() {
        Func<MyContext, object> factory;
        _factories.TryGetValue(typeof(T), out factory);
        return factory; }

    public Func<MyContext, object> GetRepositoryFactoryForEntityType<T>()
        where T : class {
        return GetRepositoryFactory<T>() ?? DefaultEntityRepositoryFactory<T>(); } }

IRepository.cs

public interface IRepository<T> : IDisposable
    where T : class
{
    IList<T> All();
    IList<T> Find(Func<T, bool> f);
    T Add(T t);
    T Update(T t);
    T Remove(T t);
}

Repository.cs

internal class Repository<T> : IRepository<T> where T : class
{
    internal MyContext Context;

    public Repository(MyContext context) { Context = context; }

    public IList<T> All() { return Context.Set<T>().ToList(); }

    public IList<T> Find(Func<T, bool> f) {
        return Context.Set<T>().Where(f).ToList();
    }

    public T Add(T t)  { /* add but don't save */ }

    public T Update(T t) { /* update but don't save */ }

    public T Remove(T t) { /* remove but don't save */ }

    public void Dispose() { /* dispose context */  }
}

ILogRepository.cs

public interface ILogRepository : IDisposable
{
    IList<Log> All();
    IList<Log> Find(Func<Log, bool> f);
}

LogRepository.cs

internal class LogRepository : ILogRepository
{
    internal DepofisContext Context;

    public LogRepository(DepofisContext context)
    {
        Context = context;
    }

    public IList<Log> All()
    {
        return Context.Logs.ToList();
    }

    public IList<Log> Find(Func<Log, bool> f)
    {
        return Context.Logs.Where(f).ToList();
    }

    public void Dispose()
    {
        Context.Dispose();
    }
}

IExtendedRepository.cs

public interface IExtendedRepository : IRepository<Extend>
{
    Extend NewMethod(Extend t);
}

ExtendedRepository.cs

internal class ExtendedRepository : Repository<Extend>
{
    public ExtendedRepository(MyContext context)
        : base(context) { }

    public Extend NewMethod(Extend t)
    {
        return t;
    }
}

IUnitOfWork.cs

internal interface IUnitOfWork : IDisposable
{
    int Save();
}

UnitOfWork.cs

public class UnitOfWork : IUnitOfWork
{
    private MyContext Context { get; set; }
    internal IProvider Provider { get; set; }

    private IRepository<T> GetGenericRepository<T>() where T : class {
        return Provider.GetGenericRepository<T>();
    }
    private T GetCustomRepository<T>() where T : class {
        return Provider.GetCustomRepository<T>(); 
    }
    private void CreateContext() { Context = new MyContext(); }

    public UnitOfWork() {
        CreateContext();
        if (Provider == null) Provider = new Provider();
        Provider.DbContext = Context;
    }

    public IRepository<User> Users { 
        get { return GetGenericRepository<User>(); } }
    public IRepository<Role> Roles { 
        get { return GetGenericRepository<Role>(); } }
    public ILogRepository Logs { 
        get { return GetCustomRepository<ILogRepository>(); } }
    public IExtendedRepository Extends { 
        get { return GetCustomRepository<IExtendedRepository>(); } }

    public int Save() { return Context.SaveChanges(); }
    public void Dispose() { /* dispose context */ }
}

As for the usage:

using(var uow = new UnitOfWork()) {
    var users = uow.Users.All();

    // do magic stuff here ;)

    uow.Save(); // don't forget to save
}

Now the uow have these methods:

  • uow.Users
    • .All()
    • .Find(q=>q.UserId == 1)
    • .Add(new User())
    • .Update(user)
    • .Remove(user)
  • uow.Logs
    • .All()
    • .Find(q=>q.LogId == 1)
  • uow.Extends
    • Same methods as uow.Users
    • .NewMethod(extend)
\$\endgroup\$
6
  • \$\begingroup\$ Excellent point (i.e. composites) Esteban; thank you, I appreciate your feedback. I'm not clear on how moving Id to all derived entities helps (Is there some entity framework side effect I am unaware of? You made reference to entity framework's handling of many-to-many relationships, did I miss/misunderstand some key point?). If I may ask, what are your thoughts on my use of generics for all data access objects (i.e. context, repository, and unit of work)? I did this to be able to use any entity implementing an interface (e.g. stubs, mocks, etc.) - is this overkill do you think? \$\endgroup\$ Commented Sep 26, 2013 at 16:32
  • \$\begingroup\$ I've updated que answer, the code I provided is a modified version of John Papa's CodeCamper, you can find it on GitHub. \$\endgroup\$
    – Esteban
    Commented Sep 26, 2013 at 22:03
  • \$\begingroup\$ Got your message, thanks for getting back to me; I'll look at this over the weekend - I appreciate your feedback. \$\endgroup\$ Commented Sep 27, 2013 at 14:22
  • \$\begingroup\$ This code is going to take a while to digest; thanks for posting it, it'll be something I reference and learn [from] over time - I appreciate it. \$\endgroup\$ Commented Sep 27, 2013 at 14:40
  • \$\begingroup\$ Glad I could help, if you need anything just ask, no worries, it took me a while to fully grasp these two patterns together. \$\endgroup\$
    – Esteban
    Commented Sep 27, 2013 at 15:21

Your Answer

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

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