HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Model Design for MVC4 Applications using Entity Framework Code First

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
mvc4designapplicationsfirstforusingcodeframeworkmodelentity

Problem

I would like to put the following design to your consideration. This is the pattern I use to follow when I'm building a Model and Data Access Layer for an MVC Application (using Entity Framework 5). I'm looking for any advises about how to improve me design (feel free to provide a new one) because I feel that it can be done better.

I want to focus on a model to be consumed for MVC Applications using Entity Framework 5 Code First, because this way we have a more specific scenario.

First I have the interface IEntity. All entities in my model implement it and this way is ensured that all entities have a numeric key.

interface IEntity
{
    int Id { get; set; }
}


Next I define entities like this:

class Person : IEntity
{
    public virtual int Id {get;set;}
    public virtual string Name {get;set;}
    public virtual int Age {get;set;}

    public virtual int PetId {get;set;}
    public virtual Pet Pet {get;set;}
}

class Pet : IEntity
{
    public virtual int Id {get;set;}
    public virtual string Name {get;set;}

    public virtual int OwnerId {get;set}
    public virtual Person Owner {get;set}
}


This is the important part:

I use a mix between Unit of Work and Repository to build a Data Access layer that let me decouple the Business logic from my data.

The abstract definition of a Unit of Work is like this:

public interface IUnitOfWork
{
    int SaveChanges();
}


In this case I need a repository for People and other for Pets.

interface IPersonRepository
{
    // Here I declare each method that my application needs.
    // Each database dependent implementation will implement those methods.
    void Add(Person person);
    void Edit(Person person);
    void Remove(Person person);
    void RemoveById(int id);
    IQueriable GetByName(string name);
}

interface IPetRepository
{
    // Similar to IPersonRepository
}


Now in my case I build a concrete implementation of my repositories and UoW using Entity Framework.

```
public

Solution

I've used this kind of approach myself in the past and it's worked well. However lately I've been considering whether it's overkill. Considering Entity framework is already following the repository and unit of work pattern you are essentially just adding the same pattern over the same pattern and that may potentially not give you any extra benefit.

I've tended to find myself using more of a service architecture while still using dependency injection etc to manage the dependencies into my various classes. I've found it's meant less layers without losing any benefits of abstraction and TDD. It will still allow you to decouple your business logic from the data access whilst not having that extra layer of complexity.

I'm also not sure about enforcing all your entities to requiring an Id. There are plenty of situations when Id's are not required on model table definitions so to enforce such a restriction seems to me potentially limiting? Situations such as link tables, or where a table might have a composite primary key rather than just an Id field.

In saying all that, nice implementation. Seems to follow conventions of the patterns you are hoping to achieve.

As requested here's an example of abstracting away EF as much as I was able whilst not requiring a seperate repository layer and using a service type approach instead (don't sue me for spelling errors :))


NOTE: Example was using Code first methodology of Entity Framework.

public interface IDataContext
{
    IDbSet Addresses { get; set; }
    IDbSet Contacts { get; set; }

    System.Data.Entity.Database Database { get; }
    DbEntityEntry Entry(TEntity entity) where TEntity : class;
    int SaveChanges();
}

public class MyDataContext : DbContext, IDataContext
{
    public IDbSet Addresses { get; set; }
    public IDbSet Contacts { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove();
        modelBuilder.Conventions.Remove();
    }
}


Using a service layer is primarily where I will now store my business logic

public class BaseService
{
    protected readonly IDataContext DataContext;
    public BaseService(IDataContext dataContext) { DataContext = dataContext; }     
}


Potentially for mocking I might consider creating an interface for each service I create

public interface IAddressService
{
    IEnumerable All(int id);
    Address GetById(int id);
    IEnumerable GetAddressWithinRange(string street, int rangeInMetres);
}

public class AddressService : BaseService, IAddressService
{
    public AnalogService(IG5DataContext dataContext) : base(dataContext)
    {
    }

    public IEnumerable All()
    {
        return DataContext.Addresses.Where(p => !p.Deleted).ToList();
    }

    public Address GetById(int id)
    {
        return DataContext.AnalogInputs.Find(id);
    }

    public IEnumerable GetAddressWithinRange(string street, int rangeInMetres)
    {
        return DataContext.Addresses
            .AsQueryable()
            .Where(p => p.Street == street && p.DistanceFromCentre  AsQueryable(Machinery machinery)
    {
        return DataContext.Entry(machinery).Collection(v => v.AnalogReadings).Query();
    }
}


Then using an IOC container (Ninject, AutoFac, Unity are a few I've used. Or see this blog by Scott Hanselman for a list of what's around) I would inject these into my controller. The setup of the dependency registration would
be dependant on the IOC implementation.

public class AddressController
{
    private readonly IDataContext _dataContext;
    private readonly IAddressService _addressService;

    public AddressController(IDataContext dataContext, IAddressService addressService)
    {
        _dataContext = dataContext;
        _addressService = addressService;
    }

    [HttpGet]
    public ActionResult Index()
    {
        var addresses = _addressService.All();

        return View("Index", addresses);
    }

    [HttpGet]
    public ActionResult Details(int addressId)
    {
        var address = _addressService.GetById(id);

        if(address == null)
            return HttpNotFound();

        return View("Address", address);
    }

    [HttpPost]
    public ActionResult Details(Address address)
    {
        var model = _addressService.GetById(address.Id);

        if(address == null)
            return HttpNotFound();

        // I might consider using A mapping framework like AutoMapper here.  Not sure if this is correct
        // syntax but hopefully you get the point
        Mapper.Map(address, model);         
        _dataContext.SaveChanges();

        return RedirectToAction("Details");
    }
}

Code Snippets

public interface IDataContext
{
    IDbSet<Address> Addresses { get; set; }
    IDbSet<Contact> Contacts { get; set; }

    System.Data.Entity.Database Database { get; }
    DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
    int SaveChanges();
}

public class MyDataContext : DbContext, IDataContext
{
    public IDbSet<Address> Addresses { get; set; }
    public IDbSet<Contact> Contacts { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
    }
}
public class BaseService
{
    protected readonly IDataContext DataContext;
    public BaseService(IDataContext dataContext) { DataContext = dataContext; }     
}
public interface IAddressService
{
    IEnumerable<Address> All(int id);
    Address GetById(int id);
    IEnumerable<Address> GetAddressWithinRange(string street, int rangeInMetres);
}

public class AddressService : BaseService, IAddressService
{
    public AnalogService(IG5DataContext dataContext) : base(dataContext)
    {
    }

    public IEnumerable<Address> All()
    {
        return DataContext.Addresses.Where(p => !p.Deleted).ToList();
    }

    public Address GetById(int id)
    {
        return DataContext.AnalogInputs.Find(id);
    }

    public IEnumerable<Address> GetAddressWithinRange(string street, int rangeInMetres)
    {
        return DataContext.Addresses
            .AsQueryable()
            .Where(p => p.Street == street && p.DistanceFromCentre < rangeInMetres);
            .ToList();
    }

    // Other business methods here

    private IQueryable<Address> AsQueryable(Machinery machinery)
    {
        return DataContext.Entry(machinery).Collection(v => v.AnalogReadings).Query();
    }
}
public class AddressController
{
    private readonly IDataContext _dataContext;
    private readonly IAddressService _addressService;

    public AddressController(IDataContext dataContext, IAddressService addressService)
    {
        _dataContext = dataContext;
        _addressService = addressService;
    }

    [HttpGet]
    public ActionResult Index()
    {
        var addresses = _addressService.All();

        return View("Index", addresses);
    }

    [HttpGet]
    public ActionResult Details(int addressId)
    {
        var address = _addressService.GetById(id);

        if(address == null)
            return HttpNotFound();

        return View("Address", address);
    }

    [HttpPost]
    public ActionResult Details(Address address)
    {
        var model = _addressService.GetById(address.Id);

        if(address == null)
            return HttpNotFound();

        // I might consider using A mapping framework like AutoMapper here.  Not sure if this is correct
        // syntax but hopefully you get the point
        Mapper.Map(address, model);         
        _dataContext.SaveChanges();

        return RedirectToAction("Details");
    }
}

Context

StackExchange Code Review Q#46884, answer score: 3

Revisions (0)

No revisions yet.