Object Mappers – Merge Objects Together

The idea of mapping objects together is quite simple. It’s like a “merge” two objects into one single object. AutoMapper is one tool that helps you achieve this and I will explain some use cases for it.

In most multi-layer systems, there are at least two instances of each database object (or entity). These “instances” are POCOs (Plain Old Class Objects) and are simply classes. These POCOs are used in different parts of the application and have different purposes.

POCOs

For example, a User POCO might be used in the repository layer of the system. The User class might look something like this.

namespace App.Domain
{
    public partial class User
    {
        public int Id { get; set; }
        public string UserName { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public bool IsVisible { get; set; }
    }
}

The fields of the User POCO are drawn from the database and reflect the structure of the User table. However, when we want to use User data in the UI layer, we may want to incorporate more fields or decorations (annotations) to store additional information used within the UI. Therefore, we need another User POCO which we can modify and that does not impact the data layer POCO. These UI POCOs are often referred to as ViewModels.

ViewModels

General ViewModel best practice says that we should prefix the POCO name with ViewModel. For example, the data POCO would be called User, whereas the UI POCO would be called ViewModelUser. The ViewModelUser POCO might look something like this.

namespace App.Web.Models
{
    public partial class ViewModelUser
    {
    	[Required("This is required")]
    	[DisplayBrand("Id")]
    	public int Id { get; set; }
    
    	[Required("This is required")]
    	[StringLength(50, ErrorMessageResourceName="ValidatedMaxLength", ErrorMessageResourceType=typeof(Generic))]
    	[Display("UserName")]
    	public string UserName { get; set; }
    
    	[Required("This is required")]
    	[StringLength(50, ErrorMessageResourceName="ValidatedMaxLength", ErrorMessageResourceType=typeof(Generic))]
    	[Display("FirstName")]
    	public string FirstName { get; set; }
    
    	[Required("This is required")]
    	[StringLength(50, ErrorMessageResourceName="ValidatedMaxLength", ErrorMessageResourceType=typeof(Generic))]
    	[Display("Surname")]
    	public string LastName { get; set; }
    
    	[Required("This is required")]
    	[StringLength(100, ErrorMessageResourceName="ValidatedMaxLength", ErrorMessageResourceType=typeof(Generic))]
    	[Display("Email")]
    	[RegularExpressEmailAttribute]
    	[DataType(DataType.EmailAddress)]
    	public string Email { get; set; }
    
    	[Required("This is required")]
    	[Display("Active?")]
    	public bool IsVisible { get; set; }
    }
}

You can see that the fields are essentially the same, but we can do more with them. Things that are specific to the UI portion of the application. If we were to just use one POCO throughout the entire system, the data POCO would have data annotation decorations for use in the UI layer which are no use in data layer. This adds to data transfer and increases the payload as POCOs are used throughout the system. The good old YAGNI approach (You Ain’t Gonna Need It) should be in the back of your mind. If you don’t need it, do not use it.

Now, a problem presents itself. When we are working with Controllers and Builders in our UI, we need to pass POCOs to the data layer. But our UI POCOs are not the same as our data POCOs. Even though we know that the User and ViewModelUser POCOs are essentially the same, our code does not know. So we need to tell it. How do we pass POCOs from our UI builders to our business builders when the parameter types do not match? This is where our Object Mapper comes into play.

We could do something like this.

namespace App.Web.Models
{
    public class ViewModelUserBuilder : IViewModelUserBuilder
    {
        private readonly IUserBusiness _userBusiness;

        public ViewModelUserBuilder(IUserBusiness userBusiness)
        {
            _userBusiness = userBusiness;
        }

        public List<ViewModelUser> GetAllUsers()
        {
            List<User> model = _userBusiness.GetAllUsers().ToList();	//	returns a list of Users (data POCOs)
			
			List<ViewModelUser> entities = new List<ViewModelUser>();	//	create a list of ViewModelUser (UI POCOs)
			
			foreach(User user in model)
			{
				//	loop through the data POCOs and create an instance of a UI POCO
				ViewModelUser entity = new ViewModelUser(){
					Id = user.Id,
					UserName = user.UserName,
					FirstName = user.FirstName,
					LastName = user.LastName,
					Email = user.Email,
					IsVisible = user.IsVisible
				}
				entities.Add(entity);	//	Add the UI POCO to the list
			}
			
            return entities;	//	return the UI POCO list for use in the UI
        }
    }
}

Object Mapper

That seems like a lot of work, right? AutoMapper can make that whole process so much easier. It’s as simple as creating an instance of each object (or list of objects), passing them into the mapping engine and returning the transformed object(s). Let me show you the above code using AutoMapper.

namespace App.Web.Models
{
    public class ViewModelUserBuilder : IViewModelUserBuilder
    {
        private readonly IUserBusiness _userBusiness;

        public ViewModelUserBuilder(IUserBusiness userBusiness)
        {
            _userBusiness = userBusiness;
            Mapper.CreateMap<ViewModelUser, User>();
            Mapper.CreateMap<User, ViewModelUser>();
        }

        public List<ViewModelUser> GetAllUsers()
        {
            var entities = new List<ViewModelUser>();
            var model = _userBusiness.GetAllUsers().ToList();
            entities = Mapper.Map(model, entities);	//	Let automapping "merge" the two objects
            return entities;
        }
    }
}

That’s much easier, right?

The order that you pass the POCOs into the Mapper.Map method does matter. The POCO on the left is the source type, and the one on the right is the destination type and the mapping works because both POCOs have the same field names. So, the mapping looks in the source POCO, then looks in the destination POCO for a corresponding field. If it finds a match, it merges the data from the source into the destination. It does not alter the source, so you can still use it if you want. But the destination POCO is presented in a useful way for you to work with.

Til next time …


16 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *