Mapper Extensions

This is documentation for the latest builds of Mapper Extensions. The API has been (mostly) updated to use a fluid API. For documentation of the last release, which uses a non-fluid API, see the legacy documentation.

Summary

Mapper Extensions is a simple object mapper implemented as a set of extension methods. Its goal is to map properties that belong to one object to another object, such as one might need to facilitate the use of Data Transfer Objects (DTOs).

Index

What does Mapper Extensions Do?
Code Samples
Simple Mapping
Map()
Data Transformation Based On Type
Data Transformation Based On Property Name
Aliased Names
Type Conversion
Exclusions
Map Enumeration To Enumeration
Chaining
Flattening
Map DataTable to List<T>

What does Mapper Extensions Do?

Mapper Extensions accomplishes the following:
  • Automatically maps properties that share the same name in both objects
  • Mismatched names can be specified to allow mapping of properties that are not the same in the source and destination object
  • Trigger rules allow you to change data based on property name or data type
  • Properties that exist in the source object, but not the destination object are simply not mapped
  • Implemented as a set of extension methods
  • Properties can be explicitly excluded from mapping
  • Exclusion lists can use simple wildcards to exclude a whole set of properties (useful in some ORM scenarios)
  • Map one enumeration to another enumeration
  • Can flatten objects
  • The latest builds are very easy to use, thanks to the new fluid API
  • Map DataTable to List<T>

Code Samples

The next few code samples will use the following simple classes to test the extension methods:

    public class Employee
    {
        public int EmployeeId { get; set;}
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime HireDate { get; set; }
        public DateTime? TerminationDate { get; set; }

    }

    public class EmpDTO
    {
        public int EmployeeId { get; set; }
        public string FName { get; set; }
        public string LName { get; set; }
        public DateTime HireDate { get; set; }
        

        public EmpDTO()
        {
            
        }

        public EmpDTO(int employeeId, string firstName, string lastName, DateTime hireDate)
        {
            EmployeeId = employeeId;
            FName = firstName;
            LName = lastName;
            HireDate = hireDate;
        }
   }

Here is the sample instance we'll use in the following examples:

            var emp = new Employee
            {
                EmployeeId = 1,
                FirstName = "John",
                LastName = "Smith",
                HireDate = DateTime.Parse("8/6/2007"),
                TerminationDate = DateTime.Today
            };

Simple Mapping

Copies values from source object to destination project if the property name matches

            var dto = emp.Map<EmpDTO>();

In this example all of the properties that exist in both the source object and destination object get copied. In this case "EmployeeId" and "HireDate" are copied over because they exist in both the source object and the destination object.

Here's a similar example:

            var dto = new EmpDTO("Test", "Test", "Test", "Test", "Test");

            emp.Map(dto);

The key difference here was that the destination object (dto) was passed in. In this example we did not need to do this because a parameterless constructor (default constructor) was available. If one had not been available, we would be forced to use this second approach.

Map()

Executes the mapping from source object to destination object, based on configuration.

The Map() method is what actually executes the mapping from source object to destination object. In the above example it was called directly (via extension method) because there was no configuration. As you will see below though, several configuration methods can be chained together (via the fluid API) before calling the Map() method. No mapping occurs until Map() is called.

Data Transformation Based On Type

Apply a data transformation before it gets copied from the source to the destination, based on the data type of the property


            var dto = emp.WithTypeTrigger(typeof (int), i => (int) i + 1000)
                .Map<EmpDTO>();

In the above example, any integers that are encountered in the source object will have 1000 added to their value before copying over to the destination object.

Data Transformation Based On Property Name

Apply a transformation to the destination property if the source's property matches a specified transformation rule

In some cases we do not want to transform data based on type, but rather based on a set of properties. In the example below we do not want to transform all integers, only EmployeeId, to which we will add 10000.

            var dto = emp.WithPropertyNameTrigger("EmployeeId", i=>(int)i+10000).Map<EmpDTO>();            

If you prefer to not use strings, you can also specify the property trigger via an expression, for example:

           var dto = emp.WithPropertyNameTrigger<Employee>(e=>e.EmployeeId, i => (int)i + 10000).Map<EmpDTO>();

Aliased Names

A destination object need not have the same property names as the source object. If names do not match then a list of aliases must be provided to the mapping extension method

            var dto = emp.WithAlias("FName", "FirstName")
                .WithAlias("LName", "LastName")
                .Map<EmpDTO>();

In the above example, the values for "FirstName" and "LastName" in the source object are copied to "FName" and "LName" in the destination object. Notice how two aliases were chained together. Chaining is a new feature to 2.0. Any rule can be chained together. This will be discussed later on.

Aliasing can be accomplished via expressions instead of strings. Below is the equivalent of the above example:

                   var dto = emp.WithAlias<EmpDTO, Employee>(d=>d.FName, e=>e.FirstName)
                        .WithAlias<EmpDTO, Employee>(d => d.LName, e => e.LastName)
                        .Map<EmpDTO>();

Type Conversion

Property types between source object and destination object do not have to match

Property types between source and destination do not have to match. The below example high-lites this by demonstrating a conversion from date properties in the source object to string properties in the destination object. It also demonstrates how different rules can be chained together.


            var dto = emp.WithAlias("HireDateLongString", "HireDate")
                .WithAlias("TermDateLongString", "TerminationDate")
                .WithPropertyNameTrigger("HireDate", d => String.Format("{0:D}", d))
                .WithPropertyNameTrigger("TerminationDate", d => String.Format("{0:D}", d))
                .Map<EmpDTO>();

Exclusions

Your destination object might contain properties that you do not want mapped. To exclude properties from being mapped, include them in the Exclusions list. Below is an example:

       var dto = emp.WithExclusion("EmployeeId").Map<EmpDTO>();


In this example the "EmployeeId" property in the emp object is not mapped to "EmployeeId" in the dto object.

Specific properties can be excluded by using expressions instead of strings. The below example is equivalent to the last example.

        var dto = emp.WithExclusion<Employee>(e=>e.EmployeeId).Map<EmpDTO>();


Exclusions also allow for simple wildcards. This is useful in some ORM scenarios, particularly if the ORM adds "baggage" to your class (e.g. Entity Framework 1.0 adds "Reference" objects to your class. If you want to map from one EF object to another EF object, you may not want to map the Reference objects. The wildcard allows you to eliminate all Reference Object mappings by including "*Reference" in the exclusion list). Below is an example:

        var dto = emp.WithExclusion("*Id").Map<EmpDTO>();

In this example, any property in the destination that ends with "Id" will not be mapped.

Map Enumeration To Enumeration

Mark G William (http://www.codeplex.com/site/users/view/markgwilliam) submitted a wrapper to map enumerations to new enumerations via extension method. This has been adapted
in the 2.0 release as well. To initiate an enumeration to enumeration mapping, first call the ForGroup() method, which extends IEnumerable<T>, e.g.

            var emps = new List<Employee>{emp};
            var dtos = emps.ForGroup().Map<EmpDTO>();

ForGroup() allows for the same chaining of rules as non enumeration objects, except it applies the rules to a collection of objects rather than a single object.

Here's an example:
            var dtos = emps.ForGroup()
                .WithAlias("HireDateLongString", "HireDate")
                .WithPropertyNameTrigger("HireDate", d => String.Format("{0:D}", d))
                .Map<EmpDTO>();

Note: To use enumeration to enumeration mapping, the target class must have a parameterless constructor (default constructor). If this is not an option, you can always map using object to object mapping in a loop, adding each target object to a list.

Chaining

As you have already seen, Mapper Extensions uses a fluid API which allows you to chain rules together. Here is a complex example
            var dto = emp.WithAlias("FName", "FirstName")
                .WithAlias("LName", "LastName")
                .WithExclusion("TerminationDate")
                .WithTypeTrigger(typeof (int), i => (int) i + 1000)
                .Map<EmpDTO>();

Flattening

Compress the object graph in a source object into a single level in the destination object

Often times you'll want to flatten an object graph into something more simple (e.g. a ViewModel in MVC).

There are two ways to flatten objects in Mapper Extensions.

The first methodology is based on naming convention. It a assumes a prefix in the destination object. If none is provided it assumes that the prefix will be the source property name.

For instance, assume a source class that looks like the following:

       class Employee
       {
              public string FirstName {get; set;}
              public string LastName {get; set;}
              public int EmployeeId {get; set;}
              public Employee Supervisor {get; set;}
       }

Notice that this class has a "complex" property, another Employee representing the supervisor.

A flattened object might look something like this:
       class Employee
       {
              public string FirstName {get; set;}
              public string LastName {get; set;}
              public int EmployeeId {get; set;}
              public SupervisorFirstName {get; set;}
              public SupervisorLastName {get; set;}
       }

To flatten out Supervisor into SupervisorFirstName and SupervisorLastName, we can use the default Flatten() functionality:
   var dto = emp.Flatten("Supervisor").Map<EmpDTO>();

This assumes that the destination object will have a prefix of "Supervisor" for all of the source properties that should be mapped.

The equivalent to the above examples, using an expression instead of a string is:
   var dto = emp.Flatten<Employee>(e=>e.Supervisor).Map<EmpDTO>();

If the destination object uses a different prefix, it can be provided. Suppose the prefix "Sup" is used. The following would accomplish the mapping:

   var dto = emp.Flatten("Supervisor", "Sup").Map<EmpDTO>();

The equivalent to the above examples, using an expression instead of a string is:

   var dto = emp.Flatten<Employee>(e=>e.Supervisor, "Sup").Map<EmpDTO>();

If you wish to not map certain properties, you can provide an exclusion list of destination property names that should not be mapped. Using the first example, let's prevent the mapping of "SupervisorLastName":

    var dto = emp.Flatten("Supervisor", "Supervisor", new List<string>{"SupervisorLastName"}).Map<EmpDTO>();

The equivalent to the above examples, using an expression instead of a string is:

   var dto = emp.Flatten<Employee>(e=>e.Supervisor, "Sup", new List<string>{"SupervisorLastName"}).Map<EmpDTO>();

Note: Automatic flattening only up to a level. It will not drill deep into objects. For instance, you can't obtain the supervisor's supervisor first name (your boss's boss), via this method.

For deeper than one level mapping, or to apply mapping rules when flattening, you'll need to use the second approach, where you provide the mapping functionality yourself. The following shows an example. It also demonstrates that flattening can also be applied to enumerations, via the ForGroup() method:

            var dtos = emps.ForGroup().Flatten((e) =>
            {
                var employee = e as Employee;
                var dtoEmp = new EmpDTO();

                if (employee.Supervisor != null)
                {
                    dtoEmp.SupervisorFullName = employee.Supervisor.LastName + ", " +
                                                employee.Supervisor.FirstName;
                }

                return dtoEmp;
            }).Map<EmpDTO>();


The above code creates a collection of DTOs. Each DTO will have a SupervisorFullName, which was constructed by appending the source object's Supervisor.LastName and Supervisor.FirstName before mapping to the destination object.

Map DataTable to List<T>

Maps ADO.net DataTables to List<T>

Note: DataTable mapping does not use the new fluent interface introduced in Mapper Extensions 2. If there is enough demand, the library can be enhanced to add this feature in.

Mapper Extensions can map DataTables to object lists, similarly to how it maps objects to objects.

Example mapping a DataTable with Aliases:

     var list = myDataTable.MapToObjectList<TestClass>(new Aliases {{"MyString", "MYSTRING"}}, null );
The above aliases "MYSTRING" in the DataTable with "MyString" in the object.

Example mapping a DataTable with an Alias and Data Trigger

   var list = myDataTable.MapToObjectList<TestClass>(new DataTypeTriggers{{typeof(int), i=>(int)i+1000}},new Aliases { { "MyString", "MYSTRING" } }, null);
The above aliases "MYSTRING" in the DataTable with "MyString" in the object and adds 1000 to all integers encountered in the DataTable.

Example mapping a DataTable with an Alias and Property Name Trigger
    var list = myDataTable.MapToObjectList<TestClass>(new PropertyNameTriggers() { { "MYSTRING", s => (string)s + "!!!" }, {"MyInt2", i=>(int)i+1000} }, new Aliases { { "MyString", "MYSTRING" } }, null);
The above aliases "MYSTRING" in the DataTable with "MyString" in the object, appends "!!!" to "MyString" and adds 1000 to "MyInt2".

Last edited Apr 23, 2011 at 7:41 PM by ggalbo, version 29

Comments

ggalbo Aug 30, 2010 at 4:43 AM 
@markgwilliam your code has been added to the project. Thanks!

markgwilliam Aug 13, 2010 at 5:49 PM 
Nice work dude. I've used this with a little wrapper to handle multiple objects: (arrays, lists, etc.)

public static class GroupMapper
{
public static IEnumerable<T> MapGroup<T>(this IEnumerable<object> source) where T : class , new()
{
return source == null ? null : source.Select(o => o.MapProperties<T>()).ToList();
}
}

Then I can do something like this:

var users = new User[]
{
new User() {Name = "Peter", Age = 36},
new User() {Name = "Lois", Age = 35}
};

return users.MapGroup<UserDetails>();