Search code examples
c#genericsdesign-patternsreflectionn-tier-architecture

Generic Wrapper around Generic Repositories using Reflection


I'm trying to practise loose coupling and seperating everything in an n-tier architecture, but I'm stuck on - what I believe - basic stuff. My number one issue is references. I'm constantly moving classes between assemblies because Class A needs Class B, but can't reach it, so lets move Class B -- and then I break Class C.

This is the best I could come up with.

Step 1: Architecture

Project.Data

  • Entities (POCO)

Project.DataAccess

  • Context
  • Migrations
  • Repositories
  • Unit of work
  • ViewModels

Project.Web

  • Controllers
  • Views

Step 2: Use Project.DataAccess as glue between Presentation and Data

I am using the Unit of Work pattern, but that requires access to the POCOs, so I can't use the UoW in my Controller. Therefore I thought it would be a good plan to create wrapper/service called ViewModelService. This service instantiates the UoW and returns AutoMapped Viewmodels to my controller.

However ...

My UoW/Repository patterns is generic, so I'm trying to make my service generic too.

IRepository

public interface IRepository<TObject>
{
    IQueryable<TObject> All();
    IQueryable<TObject> Filter(Expression<Func<TObject, bool>> predicate);

    IQueryable<TObject> Filter<TKey>(Expression<Func<TObject, bool>> filter,
        out int total, int index = 0, int size = 50);

    bool Contains(Expression<Func<TObject, bool>> predicate);
    TObject Find(params object[] keys);
    TObject Find(Expression<Func<TObject, bool>> predicate);
    TObject Create(TObject t);
    int Delete(TObject t);
    int Delete(Expression<Func<TObject, bool>> predicate);
    int Update(TObject t);
    void Ignore(TObject t);

    int Count { get; }
}

Generic BaseRepository

public class BaseRepository<TObject> : IRepository<TObject>
    where TObject : class
{
    protected AppDbContext Context = null;

    public BaseRepository(AppDbContext context)
    {
        Context = context;
    }

    protected DbSet<TObject> DbSet
    {
        get { return Context.Set<TObject>(); }
    }

    public virtual int Count
    {
        get { return Queryable.Count<TObject>(DbSet); }
    }

   // ... (You get the picture)
}

UnitOfWork

public class UnitOfWork : IDisposable
{
    private readonly AppDbContext _context = new AppDbContext();

    private BaseRepository<Order> _orderRepository;
    private BaseRepository<Product> _productRepository;
    private BaseRepository<ApplicationUser> _userRepository;
    private bool _disposed;

    public BaseRepository<Order> OrderRepository
    {
        get
        {
            if (_orderRepository == null)
            {
                _orderRepository = new BaseRepository<Order>(_context);
            }
            return _orderRepository;
        }
    }

    public BaseRepository<Product> ProductRepository
    {
        get
        {
            if (_productRepository == null)
            {
                _productRepository = new BaseRepository<Product>(_context);
            }
            return _productRepository;
        }
    }

    public BaseRepository<ApplicationUser> UserRepository
    {
        get
        {
            if (_userRepository == null)
            {
                _userRepository = new BaseRepository<ApplicationUser>(_context);
            }
            return _userRepository;
        }
    }

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

    public void Save()
    {
        _context.SaveChanges();
    }

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

So right now - without the "service" and the new n-tier layers - I use this in my Controllers.

 public class ProductController : Controller
{
    private UnitOfWork _unitOfWork = new UnitOfWork();

    // GET: /Product/
    [Route]
    public ActionResult Index()
    {
        var model = _unitOfWork.ProductRepository.All();
        return View(model);
    }

// Etc...

But now that I am dividing everything up into seperate layers, I can't do that. And I don't want to have my Unit of Work class to map the ViewModels.

Here is my attempt to create this "service" (not sure it's even the correct name for it):

ViewModelService

 public class ViewModelService : IViewModelService
 {
    private readonly UnitOfWork _unitOfWork = new UnitOfWork();

    public T GetSingle<T>(int key)
    {
        // Get appropriate repository based on T1?

        throw new System.NotImplementedException();
    }

    public IQueryable<T> GetAll<T>()
    {
        throw new System.NotImplementedException();
    }
}

Now I am faced with the issue -- how do I make sure that when I call:

_viewModelService.GetSingle<ProductVM>(id);

it figures out - by itself (via reflection?) - that it should call:

_unitOfWork.ProductRepository.Find(id)

internally inside the repository?

Wow, I feel I did a terrible job explaining that! :)

TL;DR

I have a UnitOfWork class:

 public class UnitOfWork : IDisposable
{
    private readonly DbContext _context = new DbContext();

    private BaseRepository<Order> _orderRepository;
    private BaseRepository<Product> _productRepository;
    private BaseRepository<ApplicationUser> _userRepository;
    private bool _disposed;

    public BaseRepository<Order> OrderRepository
    {
        get
        {
            if (_orderRepository == null)
            {
                _orderRepository = new BaseRepository<Order>(_context);
            }
            return _orderRepository;
        }
    }

    public BaseRepository<Product> ProductRepository
    {
        get
        {
            if (_productRepository == null)
            {
                _productRepository = new BaseRepository<Product>(_context);
            }
            return _productRepository;
        }
    }

    public BaseRepository<ApplicationUser> UserRepository
    {
        get
        {
            if (_userRepository == null)
            {
                _userRepository = new BaseRepository<ApplicationUser>(_context);
            }
            return _userRepository;
        }
    }

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

    public void Save()
    {
        _context.SaveChanges();
    }

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

Now I want to create a generic wrapper:

public class ViewModelService : IViewModelService
{
    private readonly UnitOfWork _unitOfWork = new UnitOfWork();

    public T GetSingle<T>(int key)
    {
        // Get appropriate repository based on T1?

        throw new System.NotImplementedException();
    }
}

How do I use reflection so that when I ask for GetSingle<ProductVM>(id) the wrapper will translate that into a call to _unitOfWork.ProductRepository.Find(id); -- so the wrapper knows to call the correct repository inside the UoW.

Whew.


Solution

  • One of the approaches is to have a generic abstract class and let the concrete child class override this specific piece that provides actual repository. This way most of the code can be put in the abstract class and still precisely provide what is needed at the child class level.

     // two generic types
     // TDTO - view model type
     // TDomain - domain type
     public abstract class AbstractViewModelService<TDTO, TDomain> : IViewModelService
     {
         private UnitOfWork _uow { get; }        
    
         public AbstractViewModelService( UnitOfWork uow )
         {
             this._uow = uow;
         }
    
         public abstract IRepository<TDomain> Repository { get; }
    
         public TDTO GetSingle<TDTO>(int key)
         {
             // Get appropriate repository based on T1?
             // it is here ->
    
             return Mapper.DynamicMap<TDTO>( this.Repository.Find( key ) );
         }
     }
    
     public class UserViewModelService : AbstractViewModelService<UserDto, User> 
     {
         public override IRepository<User> Repository
         {
             get
             {
                 return this._uow.UserRepository;
             }
         }
         ...
     }
    
     public class AccountViewModelService : AbstractViewModelService<AccountDto, Account> 
     {
         public override IRepository<Account> Repository
         {
             get
             {
                 return this._uow.AccountRepository;
             }
         }
         ...
     }