Ant
2 notes made

Object-Object Mapping

When using ASP.NET MVC and EF together it makes sense to have a data layer and model layer for good separation of concerns, however this can lead to lots of object to object mapping, this is a technique to keep this task nice and dry, specifically on saving model data to the db.

What I want from my mapper is:

  1. To register my maps statically so that they are available from anywhere in the application
  2. To be able to call a generic map function specifying the from and to objects, the mapper will know the types involved and performs the appropriate mapping
  3. To have a model which does not necessarily have the same properties as the data entity it gets its data from

To the Code

I have created an application with the basic pattern described here.

The Mapper Class

This contains the static maps collection and the functions to add a map and execute the mapping. You can put this functionality in any old class you deem appropriate and in scope for your own purposes - in real life I have a Antix.Data.DataFactory which is a container for this and other such functionality.

Maps Storage

A static dictionary containing all the maps available

private static Dictionary<Tuple<Type, Type>, object> _maps
    = new Dictionary<Tuple<Type, Type>, object>();

A quick note on the Tuple, this is a new addition to .NET 4.0, it makes a great key for your dictionaries where you need a compound value.

Add a Map

Couldn't be simpler, create a Tuple as a key using the from and to types and add the map to the collection

public static void AddMap<TFrom, TTo>(Action<TFrom, TTo> map)
    where TFrom : class
    where TTo : class {

    _maps.Add(Tuple.Create(typeof(TFrom), typeof(TTo)), map);
}

Map

This is the function that finds the right map delegate and performs the mapping, if no appropriate mapping is found then a nice exception is thrown.

public static void Map<TFrom, TTo>(TFrom from, TTo to) {

    var key = Tuple.Create(typeof(TFrom), typeof(TTo));
    var map = (Action<TFrom, TTo>)_maps[key];

    if (map == null)
        throw new Exception(
            string.Format("No map defined for {0} => {1}",
                typeof(TFrom).Name, typeof(TTo).Name));

    map(from, to);
}

Putting it together

One of the advantages of this technique is that you can nest mappings according to your inheritance/interfaces implementation in your object model. Here I have a Person data entity and two person models which serve different purposes in an application, PersonInfoModel and PersonDetailModel. PersonDetailModel is a derived class of the PersonInfoModel and the mapping for PersonDetailModel will call the mapping for PersonInfoModel, see below.

Mapper.AddMap<PersonInfoModel, Person>(
    (model, data) => {
        data.Name = string.Format("{0} {1}", model.FirstName, model.LastName);
    });
Mapper.AddMap<PersonDetailModel, Person>(
    (model, data) => {
        Mapper.Map((PersonInfoModel)model, data);
        data.JobTitle = model.JobTitle;
    });

Now to call the mapper

In MVC I would be calling the mapper in a Post where, I have the model as an argument in the Action and I've checked that the model state is valid.

Here I've written a test to check the mapping works as expected.

[TestMethod]
public void TestMap() {

    var personModel = new PersonDetailModel {
        FirstName = "Garry",
        LastName = "Snail",
        JobTitle = "Health and Safety Officer"
    };

    var person = new Person();

    // do the map
    Mapper.Map(personModel, person);

    Assert.AreEqual(
        string.Format("{0} {1}", 
            personModel.FirstName, personModel.LastName), 
        person.Name);

    Assert.AreEqual(personModel.JobTitle, person.JobTitle);

}

Calling map passing the PersonModel will map all the data onto the person object, the model is passed into a nested map by typing the model to its base class (PersonInfoModel).

Data Entity to Model Mapping

This could also be used to map a data entity to a model, and at first glance it would makes sense to do this for the same reasons described above. However, EF does not accept method delegates in its queries, so you can't do it. You could load the entire data model from the database and then call the mapper out-side of the query, but watch out, that maybe inefficient - loading unnecessary data.

Attachments

ObjectObjectMapping.zip VS 2010 C# 4.0

Charles Strahan

Hey, that's some pretty concise code.

BTW, do you have any opinions on AutoMapper?

http://automapper.codeplex.com/

Ant

@Charles Strahan I think its great, but for me I like to have full control over the mappings. Quite often my view models do not directly map to my domain models and AutoMapper config becomes more of a task


Post a Note

(required)

(required never shown)

On Twitter Follow MrAntix on Twitter

15/05/2012
WindowsAzure
Announcing the MEET Windows Azure Event! Streamed online June 7th. Register at http://t.co/bObzTAuL  #MEETAzure #WindowsAzure

10/05/2012
kevinwhinnery
Comparing Titanium and PhoneGap - A common question I get asked at developer events and conferences is how... http://t.co/Zq2eND6B

09/05/2012
brianleroux
PhoneGap goals and philosophy: http://t.co/wkq8wI2T

just now
cloudtrends
The Bing Search API on Windows Azure Marketplace is Here! http://t.co/4sxmX0rL #WindowsAzure

just now
mobiledevs
The Bing Search API on Windows Azure Marketplace is Here! http://t.co/WpcOWrz7 #WindowsAzure