22

I'm building my first MVC application in Visual Studio 2013 (MVC 5) and I'm a bit unclear on the best way to setup my model.

I've generated an entity framework model using code-first from an existing database. My first instinct was to create some intermediary classes that would be the model used by the views and have those classes work with the entity framework classes.

As I was writing the intermediary classes I realized that I was mostly just re-implementing a lot of the things that the EF classes already did just with the occasional private setter or cast from one datatype to another. So that seemed like a waste.

Is the general rule to directly use the entity framework classes as the Model for an MVC application? Or is there some benefit I'm missing for building these intermediary classes?

23

In my applications I have always separated things out, with different models for the database (Entity Framework) and MVC. I have separated these out into different projects too:

  • Example.Entities - contains my entities for EF and the DB context for accessing them.
  • Example.Models - contains MVC models.
  • Example.Web - web application. Depends on both Example.Domain and Example.Models.

Instead of holding references to other objects like the domain entities do, the MVC models hold IDs as integers.

When a GET request for a page comes in, the MVC controller performs the database query, which returns an entity. I have written "Converter" methods that take a domain entity and convert it to an MVC model. There are other methods that do the opposite (from an MVC model to a domain entity). The model then gets passed to the view, and thus to the client.

When a POST request comes in, the MVC controller gets an MVC model. A converter method converts this to a domain entity. This method also performs any validations that can't be expressed as attributes, and makes sure that if the domain entity already exists that we are updating it rather than getting a new one. The methods usually look something like this:

public class PersonConverter
{
    public MyDatabaseContext _db;

    public PersonEntity Convert(PersonModel source)
    {
         PersonEntity destination = _db.People.Find(source.ID);

         if(destination == null)
             destination = new PersonEntity();

         destination.Name = source.Name;
         destination.Organisation = _db.Organisations.Find(source.OrganisationID);
         //etc

         return destination;
    }

    public PersonModel Convert(PersonEntity source)
    {
         PersonModel destination = new PersonModel()
         {
             Name = source.Name,
             OrganisationID = source.Organisation.ID,
             //etc
         };

         return destination;
    }
}

By using these methods I take the duplication out that would otherwise occur in each controller. The use of generics can deduplicate things even further.

Doing things this way provides multiple benefits:

  • You can customise a model to a specific view or action. Say you have a signup form for a person that when submitted, creates many different entities (person, organisation, address). Without seperate MVC models this will be very difficult.
  • If I need to pass more information to the view than would otherwise be available just in the entity, or combine two entities into a single model, then my precious database models are never touched.
  • If you ever serialise an MVC model as JSON or XML, you only get the immediate model being serialised, not every other entity linked to this one.
  • Good answer, would recommend using ValueInjector or something similar (personally I hated automapper) instead of manually mapping properties from one class to the other. – Rocklan Sep 30 '14 at 2:07
  • 1
    Rather than adding a separate answer, I'll just comment here that in DDD practices, your "converters" and separate models for the view would be considered part of the Application Service Layer. Basically, it allows your Domain Model to be as complex as needed while hiding that complexity from the application. It also shields the application from having to be changed because of a change in the domain model. The ASL handles the translation. – Michael Brown Oct 3 '14 at 14:37
  • So you make a call for each model you have in your PersonModel (ie. the Organization object) to get that model's information? Say you have a form to update the person and the organization information, would you have an additional call when you update the Organization? I'm using stored procs so couldn't I send all of the model's attributes and any containing model's attributes all at once? – Luminous Apr 8 '15 at 15:22
  • 1
    How would you handle mapping back a collection? That seems to have been much more complicated in EF6 as you can no longer just create a new list of entities with the updates as this just re-creates everything... – Gerard Wilkinson Aug 17 '16 at 23:33
  • 2
    Instead of writing your own converter classes I'd recommend using the Automapper library which has been written to address this problem. It has matured a lot since 2014! – BenSmith Jan 16 '18 at 1:18
6

I would say it really depends on your application. Is it just doing pure CRUD, without any business logic? Then I would use EF models directly in my views.

Most of the time there is at least some business logic involved and then a layer between the data/EF models and the view might be a good idea. In this case it might be appropriate to do "CQRS-lite" (see below) and use different models in to and out of your controller. Most of the time the read models are a lot "fatter" than the write models...

However, if the application contains a lot of business logic and/or needs to scale a lot, I would implement at least the core of it using CQRS (Command Query Responsibility Segregation), DDD (Domain Driven Design) and possibly Event Sourcing. Then EF could be used as the read model facade.

Also remember that you don't need to stick to one strategy/pattern for the whole application, some areas may be pure CRUD and other areas may contain a lot of business logic...

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