Tappitytap.info

Property Mapper

Implementing a Property Mapper in C#

Passing data from one model to another is an undertaking that occurs often. Generally the process is quite simple matching one property with another passing the data from one property to another until all the data has been transferred. Pretty tedious stuff, especially if you are writing an application that will have minimum use or minimal impact. So to make life easier it would be useful to have something that could do this for us.

Passing data from one model to another typically happens all the time, but if the process is simply to pass one object to another object that is similar if not almost identical then why do we even need to do this at all? Well one very good reason is clean code. Applications can become complex so its generally a good idea to split things up into simpler things, layers or domains. Your domain should not depend on anything else, it should be independent.

I see this all the time, where data models from the data layer 'for want of a better name' are endemic of the whole application. this makes it very difficult to test and wholly dependent other frameworks, there benefits and there limitations.

This is of course a whole separate topic so if you would like to learn more about these principles then I would encourage you to go see Uncle Bob Clean Code Uncle Bob YouTube


public class DomainTransfer {
	public void Address(int Id, string Name, string Area, decimal Long, decimal Lat)
	{
		var Address = db.Addresses.SingleOrDefault(o => o.Id == Id)
		Address.Name = Name;
		Address.Area = Area;
		Address.Long = Long;
		Address.Lat = Lat;
		db.SaveChanges();   
	}
}

 // Call
 DT.Address(a.Id, a.Name, a.Area, a.Long, a.Lat);
			

As you can see in this very simple example there is quite a lot of repetitive work to do so what could be useful is to be able to make the call without writing all the underlying code. We could replace the code in the DomainTransfer class to


public void Address(AddressModel addressModel)
	{
		var address = db.Addresses.SingleOrDefault(a => a.Id == Id);
		Mapper<AddressModel, Address> mapper = new Mapper<AddressModel, Address>();
		Mapper.Copy(addressModel,address);
		db.SaveChanges();   
	}						
			

As you can see this is much simpler to do, just imagine if you had a class with 10 properties or more how much of a time saver this would be.

Just as an aside, this is a very simple example and there is no validation or complex logic. Keep things simple if the responsibility of this domain is to transfer data just do that.

Defining the Mapper

To make this mapper we need to be able to find the properties on the two classes what I'll call the From and To. To start with we will keep it simple and only map classes with the same property names. once we have done this then we can extend the class and make it deal with more complex situations.

As this mapper is going to deal with many different classed but we want to have good performance and the convenience of using typed instances. The best way to do this is to use generics. To specify a generic type use <T> and as we are moving data between two different classes then we will need two generic arguments.

More about Generics

public class Mapper<Tfrom, Tto>{	}

	// this allows us to instantiate the class

Mapper<AddressModel, Address> mapper = new Mapper<AddressModel, Address>();
		

In this case we are instantiating with the two different classes one is the model class and one is the domain class, what is useful to know is that these classes are interchangeable so we could pass the information the other way and it would just work.

We would also need to have a Copy method to be able to pass the instances that are going to have their data copied between each other, as we have defined Tfrom and Tto at the class level we can now use them in the arguments as we would any .net type.


public void Copy(Tfrom from, Tto to)
		

The next step is to find the properties on each class and the pair them up, to do this we can use reflection.Reflectionprovides us with a Type class which describe classes as well as other types such an Modules and assemblies. it also allows us to dynamically instance classes, call methods, access properties to name a few.

We also want to use the PropertyInfo class this gives us all the information we need and allows us to find the names of the properties and also to read/write data.


private List<KeyValuePair<PropertyInfo, PropertyInfo>> propertyMappings; //We keep a list of all the mapped properties

private void CreateMappings(Type from, Type to){ 	//method accepts two argument of Type
	PropertyInfo[] fromProps = from.GetProperties(); //retieve a list of properties 

	foreach (PropertyInfo fromPropertyInfo in fromProps) {
		string fromName = fromPropertyInfo.Name;
		
		//once we have the name of the property on the 'From' class we now find the Property on the 'To' class
		PropertyInfo toPropertyInfo = to.GetProperty(fromName); 
		if (toPropertyInfo == null) continue; //if no property found we don't have a match and move on
		KeyValuePair propertyMapping = new KeyValuePair(fromPropertyInfo, toPropertyInfo);
		propertyMappings.Add(propertyMapping); //store the information in our mappings variable
	}
}
		

So now we have a way to match up properties so all we need to do now is copy the data between the instances of the From and To classes, as the data is stored in a List of KeyPair's we iterate over the list. so all we need to do now is assign the data taking it from the 'From' property and assigning it to the 'To' property using the SetValue method of the PropertyInfo class.


public void Copy(Tfrom from, Tto to)
{
	if(propertyMappings.Count== 0) {
		CreateMappings(from.GetType(), to.GetType());
	}

	foreach (KeyValuePair<PropertyInfo, PropertyInfo> propPair in propertyMappings) {
		PropertyInfo fromPi = propPair.Key;
		PropertyInfo toPi = propPair.Value;
		toPi.SetValue(to, fromPi.GetValue(from));
	}
}
		

And to use


Mapper<Person, PersonModel> mapper = new Mapper();
mapper.Copy(addressModel, address);
		

So what else would we want. It is quite probable that some properties that we want to map don't have the same names for one reason or another. we might also want to miss out certain properties, and all we would need it to create a list of these properties and add that to the logic of the CreateMappings method


		// just a couple of lines of code
private List<KeyValuePair<string, string>> nameMappings;
public void AddMap(string from, string to){
            nameMappings.Add(new KeyValuePair<string, string>(from, to));
        }
		
provate void CreateMappings(Type from, Type to){
	PropertyInfo[] fromProps = from.GetProperties();

	foreach (PropertyInfo fromPropertyInfo in fromProps) {

		string name = nameMappings.FirstOrDefault(k => k.Key == fromPropertyInfo.Name).Value; // use the added name map data
		if (name == null) name = fromPropertyInfo.Name;
		PropertyInfo toPropertyInfo = to.GetProperty(name);
		if (toPropertyInfo == null) continue;
		KeyValuePair<PropertyInfo, PropertyInfo> propertyMapping = new KeyValuePair<PropertyInfo, PropertyInfo>(fromPropertyInfo, toPropertyInfo);
		propertyMappings.Add(propertyMapping);
	}
}
	// And to use just add a the names of the respective GetProperties
mapper.AddMap("Name", "FullName");
		

We now have a class that would allow us to do a shallow copy of the data between any two classes with matching properties or not, since most POCO objects are generally simple this is maybe all we need, no more manual property copying; it can all be done with a couple of lines of code.

Note that there are several property mappers that are quite sophisticated out in the wild, they are used widely and can be very efficient, so why would we write our own. The simple answer is that if we use a 3rd party component then we have to be very careful that we don't allow it to control our code, if we write it ourselves than we have complete control but ultimately its a choice, my personal one is that if I can write it myself then I will, and you never know you might learn something on the way. AutoMapper Dapper A more sophisticated mapper

This is a very simple example, there is no exception handling, we also might not be able to read or write all the properties as some could be readonly/writeonly, the data types may be different and unable to implicitly cast, and some may be private. but considering that in a system we would generally own all the classes that we are going to interact with this could be considered unnecessary.