Search code examples
entity-frameworkasp.net-mvc-4servicearchitecturen-tier-architecture

MVC keeping models out of Service layer - not always possible


I have an MVC project which includes a Service Layer. The service classes return db entity objects to the controllers which I use to build models which are passed to the Views. In this way my service layer has no knowledge of the models and I'm trying to keep it that way.

A typical service layer method will return IQueryable e.g:

public IQueryable<Store> GetAll()
{
   return _context.Set<Store>();
}

However sometimes I need to return data from say, 2 db entities where no db relationship exists and because the service layer can't return an anonymous type, I end up returning a model, which I'm not really happy about.

An example of this is:

public IEnumerable<CashDrawerModel> GetTillList(int? storeId)
{
var query = from c in _context.Set<CashDrawer>()
    where c.StoreId == storeId || storeId == null
    join cd in _context.Set<CashDrawerActivity>() on c.Id equals cd.CashDrawerId into joinedT
    from j in joinedT.DefaultIfEmpty()
     group joinedT by c into g
     select new CashDrawerModel
     {
      ...

How can I avoid this? Am I right to be concerned about maintaining this separation, or is it in fact indicative of poor db design and needs refactoring?


Solution

  • I don't think it's a poor db design and it's very common case when you have to query several db entites before passing the data to the views. Sometimes the difference between read and write operations is so significant that it's a good idea to follow Command Query Separation principles.

    So if I were you I would do the following:

    1. Add read models to your service layer when required. Please note they have nothing to do with the view models. They are supposed to be used in case the read result consists of several db entities as you described above.

    2. Add a corresponding view model and do the mapping between service layer read model and the view model in the controller. This way you keep your service layer separated from the presentation one. If the view model turns out to be an exact copy of the service layer read model you can skip creating view model in this case and just pass the read model to the view. However if you need to add some UI specific data which has nothing to do with service layer you will have to create a view model and do the mapping.

    3. Come up with a good naming convensions. I prefer XXXReadModel as a service layer read model and XXXViewModel as a presentation layer view model.

    So if following the steps I descibed above you should have CashDrawerReadModel which is part of the service layer. Then you should have CashDrawerViewModel which is a presentaion layer view model and do the mapping between the two in the controller. You can skip CashDrawerViewModel here in case it is identical to the CashDrawerReadModel and just use CashDrawerReadModel in a view.

    Hope it helps!