How to get from specific Model to ViewModel and vice versa, using nested classes?

I want to know a good way of converting the Model to ViewModel and ViewModel to Model without AutoMapper or something similar, because I want to understand what is behind and learn how to do it myself. Of course, by Model I mean the classes generated by EF.

I made something like this so far, but have some issues when nested classes are involved:

    // to VM
    public static Author ToViewModel(EntityAuthor author)
    {
        if (author == null)
            return null;

        Author result = new Author();
        result.Id = author.ATH_ID;
        result.FirstName = author.ATH_FirstName;
        result.LastName = author.ATH_LastName;
        return result;
    }
    public static BlogPost ToViewModel(EntityBlogPost post)
    {
        if (post == null)
            return null;

        Experiment result = new Experiment();
        result.Id = post.BP_ID;
        result.Title = post.BP_Title;
        result.Url = post.BP_Url;
        result.Description = post.BP_Description;
        result.Author = ToViewModel(post.Author);
        return result;
    }


    // from VM 
    public static EntityAuthor ToModel(Author author)
    {
        if (author == null)
            return null;

        EntityAuthor result = new EntityAuthor();
        result.ATH_ID= author.Id;
        result.ATH_FirstName = author.FirstName;
        result.ATH_LastName = author.LastName;
        return result;
    }
    public static EntityBlogPost ToModel(BlogPost post)
    {
        if (post == null)
            return null;

        EntityBlogPost result = new EntityBlogPost();
        result.BP_ID = post.Id;
        result.BP_Title = post.Title;
        result.BP_Url = post.Url;
        result.BP_Description = post.Description;
        result.Author = ToModel(post.Author);
        return result;
    }

Note: The EntityBlogPost holds the Foreign key to the EntityAuthor. One issue that I face now is when I want to edit a BlogPost, its corresponding entity requires the author's foreign key: "BP_ATH_ID" to be set, but this is '0' since the author of the edited post is 'null', because I don't want to http-post the author. Still, the author needs to be in the view-model because I want to display it (during http-get). Here is my controller to understand better (the view is not of importance):

    // GET: I make use of Author for this
    public ActionResult Edit(int id)
    {
        return View(VMConverter.ToViewModel(new BlogPostService().GetByID(id)));
    }

    //
    // POST: I don't make use of Author for this
    [HttpPost]
    public ActionResult Edit(BlogPost input)
    {
        if (ModelState.IsValid)
        {                
            new BlogPostService().Update(VMConverter.ToModel(input));
            return RedirectToAction("List");
        }
        return View(input);
    }

At the moment I have some Services behind my controller which work only over the Model (as you can see in my code). The intent was to reuse this "service layer" for other applications as well.

    public void Update(EntityBlogPost post)
    {
        // let's keep it simple for now
        this.dbContext.Entry(post).State = EntityState.Modified;
        this.dbContext.SaveChanges();
    }

Ok, so back to my question. What would be a nice way to handle this transition Model->ViewModel and back?

Answers


In my opinion the approach is problematic in both directions.

  • Model to ViewModel (GET requests)

    If you are using a method like this...

    public static Author ToViewModel(EntityAuthor author)
    

    ...the question is: Where do you get the EntityAuthor author from? Of course you load it from the database using Find or Single or something. This materializes the whole EntityAuthor entity with all properties. Do you need them all in the view? Maybe yes, in this simple example. But imagine a big Order entity with a lot of references to other entities - customer, delivery address, order items, contact person, invoice address, etc., etc. - and you want to display a view with only some properties: due date, customer name, contact person email address.

    To apply the ToViewModel method you have to load the EntityOrder with a whole bunch of properties you don't need for the view and you even have to apply Include for the related entities. This again will load all properties of those entities but you need only a selection of them in the view.

    The usual way to load only the properties you need for the view is a projection, for example:

    var dto = context.Orders.Where(o => o.Id == someOrderId)
        .Select(o => new MyViewDto
        {
            DueDate = o.DueDate,
            CustomerName = o.Customer.Name,
            ContactPersonEmailAddress = o.ContactPerson.EmailAddress
        })
        .Single();
    

    As you can see I have introduced a new helper class MyViewDto. Now you could create specific ToViewModel methods:

    public static OrderViewModel ToMyViewModel(MyViewDto dto)
    

    The mapping between dto and viewModel is a good candidate for AutoMapper. (You cannot use AutoMapper for the projection step above.)

    An alternative is to project directly into the ViewModel, i.e. replace MyViewDto above by OrderViewModel. You have to expose IQueryable<Order> to the view layer though where the ViewModels live in. Some people don't like it, personally I am using this approach.

    The downside is that you need a lot of different methods of type ToMyViewModel, basically for every view another method.

  • ViewModel to Model (POST requests)

    This is the bigger problem as you already have noticed in your example: Many views don't show full entities or show entity data that are supposed to be "view only" and don't get posted back to the server.

    If you use the method (with AutoMapper or not)...

    public static EntityAuthor ToModel(Author author)
    

    ...you obviously don't create a full EntityAuthor object in most cases because the view represented by the view model Author author doesn't show all properties and at least doesn't post them all back. Using an Update method like this:

    this.dbContext.Entry(post).State = EntityState.Modified;
    

    ...would destroy the entity partially in the database (or throw an exception in the best case because some required FKs or properties are not set correctly). The achieve a correct Update you actually have to merge values which are stored in the database and are left unchanged with changed values posted back from the view.

    You could use specific Update methods tailored to the view:

    public void UpdateForMyView1(EntityBlogPost post)
    {
        this.dbContext.EntityBlogPosts.Attach(post);
        this.dbContext.Entry(post).Property(p => p.Title).IsModified = true;
        this.dbContext.Entry(post).Property(p => p.Description).IsModified = true;
        this.dbContext.SaveChanges();
    }
    

    This would be a method for a view which only allows to edit Title and Description of an EntityBlogPost. By marking specific properties as Modified EF will only update those columns in the database.

    The alternative is to introduce DTOs again and mapping methods between view model and those DTOs:

    public static MyUpdateAuthorDto ToMyUpdateAuthorDto(Author author)
    

    This is only property copying or AutoMapper. The Update could be done by:

    public void UpdateForMyView1(MyUpdateAuthorDto dto)
    {
        var entityAuthor = this.dbContext.EntityAuthors.Find(dto.AuthorId);
        this.dbContext.Entry(entityAuthor).CurrentValues.SetValues(dto);
        this.dbContext.SaveChanges();
    }
    

    This updates only the properties which match in EntityAuthor and in dto and marks them as Modified if they did change. This would solve your problem of the missing foreign key because it is not part of the dto and won't be updated. The original value in the database remains unchanged.

    Note that SetValues takes an object as parameter. So, you could use some kind of resusable Update method:

    public void UpdateScalarAuthorProperties(int authorId, object dto)
    {
        var entityAuthor = this.dbContext.EntityAuthors.Find(authorId);
        this.dbContext.Entry(entityAuthor).CurrentValues.SetValues(dto);
        this.dbContext.SaveChanges();
    }
    

    This approach only works for updates of scalar and complex properties. If your view is allowed to change related entities or relationships between entities the procedure is not that easy. For this case I don't know another way than writing specific methods for every kind of Update.


A nice way to handle these transition would be to use AutoMapper. That is what automapper was created for, really.

If you want to learn how it works, please use assemlby decompiler (ILSpy is one of them) and use it on AutoMapper.dll.

The magic word here is Reflection.

Start with:

foreach (PropertyInfo prop in typeof(EntityAuthor).GetProperties())
{
   ...
}

Reflection mechanisms allows you to list all properties of source and destination class, compare their names and when you match these names, you can set property of destination object using SetValue method, based on values from source object.


Need Your Help

Capturing audio stream in amr-nb format Winstore app

windows-runtime

I am looking for a solution to capture audio stream in amr-nb format in a WinStore app.