The best way to see how oMapGen works (and how it can be adapted to your needs) is to tweak the sample test project that you can download from the source code page. For now let's see how it works through our existing example project.

In this case we have two sets of objects from distinct assemblies that we want to map the ones to the others:

//1st assembly contains:
namespace Example.Objects1
    public class WebAccount1 { ... }
    public class Address1 { ... }
    public class Customer1
        public List<WebAccount1> WebAccounts { get; set; }
        public Address1 Address { get; set; }

//2nd assembly contains:
namespace Example.Objects2
    public class WebAccount2 { ... }
    public class Address2 { ... }
    public class Customer2
        public WebAccount2[] WebAccounts { get; set; }
        public Address2 Address { get; set; }

One fundamental point to keep in mind is that does not direclty generate the mapper code. It must be included in another custom T4 text template. The first reason is that you need to include the generated mapper within your own name space. Also it is necessary to pass parameters for the mapper constructor and generation operations (e.g. source and destination definitions).

Simply include relative path in your custom text template like below. And since you want a .cs C# source file to be generated, you must indicate that in the header.

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>
<#@ include file="..\" #>

namespace Example.Mappers
   //mapping generation goes here

There are basically two ways to configure the mapping and generate extension methods depending on where your source and destination types are defined:

1) if they are known at compile time, you can simply pass them to the generation operation using an empty mapping instance:

//my auto-generated web account mapper
public static class WebAccountMapper
    new oMapGen().Generate(typeof(WebAccount1), typeof(WebAccount2), WriteLine, WriteLine, true);

2) if they are not known at compile type (because they are defined inside some code that is included in the current assembly for example), you must either provide the C# source file or assembly paths to the mapping instance on which you are to call the generation operation by passing the types as strings (they will be compiled and resolved in memory) :

//my auto-generated address mapper
public static class AddressMapper
   var mapper = new oMapGen(new string[] { sourceDir+"\\Address1.cs" }, new string[] { sourceDir+"\\Address2.cs" });
   mapper.Generate("Example.Objects1.Address1", "Example.Objects2.Address2", WriteLine, WriteLine, true);

//my auto-generated customer mapper
public static class CustomerMapper
   var mapper = new oMapGen(assemblyDir+"\\Example.Objects1.dll", assemblyDir+"\\Example.Objects2.dll");
   mapper.Generate("Example.Objects1.Customer1", "Example.Objects2.Customer2", WriteLine, WriteLine, true);

The Generate method takes as parameters the source and destination types you want to map, the writer actions that will be used for code and trace generation (e.g. a simple WriteLine without code formatting), and a boolean indicating if backward injection should also be generated.

If sourceDir and assemblyDir are statically set, the mapper can be directly generated either by saving the .tt file or by executing the default custom text transform tool on it (right click in Visual Studio).
But it would be better to be able to regenerate the mapper automatically during the build. This can be done by manually editing the .csproj file of the mapper project and by adding the following XML node:

<Target Name="BeforeBuild">
    <Exec Command="&quot;%CommonProgramFiles(x86)%\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\TextTransform&quot; -r &quot;..\Example.Objects1\bin\Debug\Example.Objects1.dll&quot; -r &quot;..\Example.Objects2\bin\Debug\Example.Objects2.dll&quot; -I &quot;$(ProjectDir.TrimEnd('\'))&quot;" />
    <Exec Command="&quot;%CommonProgramFiles(x86)%\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\TextTransform&quot; -I &quot;$(ProjectDir.TrimEnd('\'))&quot; -a &quot;!!objects1SourcePath!..\Example.Objects1&quot; -a &quot;!!objects2SourcePath!..\Example.Objects2&quot;" />
    <Exec Command="&quot;%CommonProgramFiles(x86)%\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\TextTransform&quot; -P &quot;..\Example.Objects1\bin\Debug&quot; -P &quot;..\Example.Objects2\bin\Debug&quot; -I &quot;$(ProjectDir.TrimEnd('\'))&quot;" />

These commands use the same text transform tool that Visual Studio does:
  • for the WebAccount case, we must simply reference the assemblies that contain the source and destination types with the -r option;
  • concerning the Address, we need to resolve the directory paths that contain the C# source code where the types are defined. For that we create the objects1SourceDir and objects2SourceDir variables with the -a option which we set with the valid values. sourceDir must then be set with a call to Host.ResolveParametersValues that will take the two variables as parameters (the hostspecific template attribute must be set to true to enable this);
  • the Customer mapping generation must be configured with the -P option to indicate the reference paths of the source and destination assemblies: the assemblyDir variable can be removed then.
In any case, the -I option is used for the directory where the text template is located.

Now we can simply call the generated mapping extension methods like this:

new WebAccount1().From(webAccount2);

new Address1().From(address2);

new Customer1().From(customer2);

You can see if our objects are correctly mapped by executing the included unit test project Example.Mappers.Test. You can also enrich this sample with your own objects, mappers and tests to see if it works out for you.

Please don't hesitate to leave a comment if there is anything unclear to you.

Last edited Apr 25, 2014 at 9:10 AM by xmamat, version 7


No comments yet.