Pergunta

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.

Foi útil?

Solução

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;
         }
     }
     ...
 }

Outras dicas

Why not to use MEF? break your UOW that it will compose the repositories - also you will benefit that you will don't have the duplication of

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

add metadata to each export item and then in

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

    throw new System.NotImplementedException();
}

you can get the correct plugin according to the metadata that it expose. or you can add all your repositories to ServiceLoactor, so you can write instead of

_unitOfWork.ProductRepository.Find(id);

ServiceLocator.Current.GetInstance<T>().find(id)
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top