In a simple application you can use a single class as EF entity, domain model, and view model.
In more complex applications all these models became different classes. For example, code first entities and ASP.NET MVC models have different annotations (KeyAttribute
in EF, DisplayFormatAttribute
in MVC), and usually you shouldn't mix them in a single class.
Also, all EF properties should be writeable, but domain properties can be read-only. MVC models can contain specific SelectList
, but domain models and entities shouldn't.
So in complex application you have separated models at each application's tier (presentation, domain, data-access), and you should map these models at tier's boundaries.
Lets look the small example of a blog. Each post in a blog has creation date/time and (imaging) a category:
EF
[TableName("PostCategories")]
public class EfCategory
{
public int CategoryId { get; set; }
public string Name { get; set; }
}
public class EfPost
{
public DateTime Created { get; set; }
public virtual Category Category { get; set; }
public int CategoryId { get; set; }
. . .
}
You see, Post.Created
is writeable and readable property, and Category
has TableName
attribute. Can you use these classes in the domain tier?
No. First, the creating date/time property is read-only in the domain, you can't change it. Second, what if you'll want rewrite data-access with NHibernate or ADO.NET? You'll can't cause your domain model Category
has the attribute TableName
.
So, you can create duplicate domain models:
Domain
public class DomainCategory
{
public int Id { get; private set; }
public string Name { get; private set; }
public DomainCategory(int id, string name)
{
Id = id;
Name = name;
}
}
. . .
public EfCategoryRepository : ICategoryRepository
{
private readonly DbContextFactory dbContextFactory;
. . .
public CategoryRepository(DbContextFactory dbContextFactory)
{
this.dbContextFactory = dbContextFactory;
}
. . .
public DomainCategory GetById(int id)
{
using (var dbContext = dbContextFactory.CreateReadOnlyContext())
{
var entity = dbContext.Set<EfCategory>.Single(c => c.Id == id);
return new DomainCategory(entity.CategoryId, entity.Name);
}
}
}
Now the domain's interface ICategoryRepository
that "knows" about domain models, is implemented by EF data access class EfCategoryRepository
. This implementation "knows" about EF models and can map EF to domain.
Domain (continue)
public class DomainPost
{
public DomainCategory { get; set; }
public DateTime Created { get; private set; }
. . .
public DomainPost(EfCategory category, DateTime created, ...)
{
Category = new DomainCategory(category.CategoryId, category.Name);
Created = created;
. . .
}
}
Domain's property Created
is read-only dislike the EF's property.
Finally, if we think about view (ASP.NET MVC) edit model, we'll find that this model should contain the list of categories and shouldn't contain Created
cause user can't change it:
Presentation
public MvcEditPost
{
public int Id { get; set; }
[Display("Category")]
public int CategoryId { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
}
public ActionResult Edit(int id)
{
var domainPost = postRepository.GetById(id);
var modelPost = new MvcEditPost
{
Id = domainPost.Id,
CategoryId = domainPost.Category.Id,
Categories = categoryRepository.GetAll()
.Select(c => new SelectListItem
{
Selected = c.Id == domainPost.Category.Id,
Text = c.Name,
Value = c.Id.ToString(),
}),
};
return View(modelPost);
}
@model MvsEditPost
@using (Html.BeginForm("Post", "Edit", { Id = Model.Id }))
{
@Html.DropDownListFor(m => m.CategoryId, Model.Categories)
}
So, the presentation code is also "knows" about both presentation and domain models and can map firsts to seconds. This model differentiation let you make all tiers independent. More precisely, a presentation tier and a data-access tier are depending on a domain tier now. So you can simple rewrite data-access (SQL Server to Oracle, EF to NHibernate, Single data-server to cloud), or you can simple rewrite presentation (MVC to Desktop, MVC to Console). You wouldn't be able to do it without models' differentiation.