Comparison of Object Mapper Libraries
Tags: csharp, webdev, dotnet, efcore
Published: 2021-05-19 09:44:41

<p>In this article we look at object to object mappers in C# .NET.&nbsp; Mappers solve the common problem of writing mundane and repetitive code when assigning values between objects.&nbsp; Three different mapping libraries are evaluated to give an idea of common themes and some differences which might be relevant for your development needs.</p>
<p>&nbsp;</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="https://ivwebsitestorprod.blob.core.windows.net/iv-website-files/arrows-GREEN_637569504131756774.png" alt="" width="342" height="342" /></p>
<p>The mapping libraries AutoMapper, AgileMapper and Mapster have been used in a simple demo program to explore some simple mapping scenarios.</p>
<p>If this is a new area for you then hopefully you'll gain some knowledge from reading this article.&nbsp; Or perhaps you have been using the same object mapper for some time; should you consider an alternative?</p>
<h2><strong>What is an object mapper and what problem does it solve?</strong></h2>
<p>Object to object mapping is the process of assigning values from one object to another.&nbsp; This is common in a layered architectures, particularly those developed using an ORM framework such as Entity Framework Core.&nbsp; There can be many instances in application code where there is a requirement to translate data between the underlying entity model classes and data transfer objects (DTOs).&nbsp; Another case where object mapping is used is between DTOs, typically from a Web API to view model classes in MVC web application.&nbsp;</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="https://ivwebsitestorprod.blob.core.windows.net/iv-website-files/Map1_637569511523691537.png" alt="Entity model class and DTO class definitions" width="690" height="198" /></p>
<p>Mapping code is quite mundane and repetitive to write and a well adopted solution is to use a mapping library.&nbsp; Mapping libraries generally work by matching members in objects and through object graphs by using a set of conventions based on name and in some cases type too.</p>
<h2><strong>Benefits of using an Object Mapper</strong></h2>
<p>Solving the object to object mapping problem with a mapping library will make your life as a developer easier. Here are some direct benefits:</p>
<ul>
<li>Not writing repetitive boring code</li>
<li>Smaller, cleaner codebase</li>
<li>Consistent naming of properties across codebase</li>
<li>It is a strategy which is well adopted by developers</li>
</ul>
<h2><strong>Comparing Object Mapping Libraries</strong></h2>
<p>In this post each of the mapping libraries are compared for the following:&nbsp;</p>
<ul>
<li>Popularity</li>
<li>Ease of use</li>
<li>Ease of configuration</li>
<li>Collections</li>
<li>Mapping and reverse mapping</li>
<li>Flattening and unflattening</li>
<li>Nested objects</li>
<li>Projections</li>
<li>Documentation</li>
</ul>
<p>For this evaluation, a C# .Net Core 5 console application was used using EntityFramework Core and SqlLite database. &nbsp;It is a simplified model of a layered architecture and&nbsp;<a title="Mapping demo project on GitHub" href="https://github.com/JD-Innovensa/MappingDemo" target="_blank" rel="noopener">available on GitHub</a>.</p>
<p>The project contains a simple <em>service</em> interface which provides a few basic scenarios to test such as a single-level object&nbsp; to object mapping, a two-level object mapping with an object containing a collection and a reverse mapping.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="https://ivwebsitestorprod.blob.core.windows.net/iv-website-files/Map2_637569511524698766.png" alt="IBookService interface definition from Mapping Demo" width="513" height="209" /></p>
<p>In the project code, the interface is implemented for each mapping solution.&nbsp; Also for good measure, the interface was implemented without using a mapper. This is the mundane code that we wish to remove by using a mapping library!</p>
<p>Mapping can get quite in-depth so not all use cases are covered, but this simple project is available and can be further extended to try out different things.</p>
<h2><strong>Object Mapping Libraries</strong></h2>
<p>Three object mapping libraries were compared: AutoMapper, AgileMapper and Mapster.&nbsp; There are a range of libraries to use, but here we have a very popular, quite popular and less well-known object mapping library for comparison.</p>
<h3><strong>AutoMapper</strong></h3>
<p>This is our current object mapper of choice. We decided to use AutoMapper on some recent projects due to its existing popularity and its use in Jon P Smith&rsquo;s practical book <a title="Entity Framework Core in Action" href="https://www.thereformedprogrammer.net/" target="_blank" rel="noopener">Entity Framework Core in Action</a>.</p>
<p>AutoMapper takes a low-configuration approach and is designed to be used in dependency injection scenarios such as in ASP.Net Core applications.</p>
<p>AutoMapper demands a configuration which can be divided into profiles and validated.&nbsp; The library can perform a scanning operation to include all profiles when the configuration is built.&nbsp; It creates an expression tree which can be inspected during debugging using an extension to view.</p>
<p>See: <a title="AutoMapper" href="https://automapper.org/" target="_blank" rel="noopener">https://automapper.org/</a></p>
<h3><strong>AgileMapper</strong></h3>
<p>AgileMapper is a zero-configuration, highly-configurable object-object mapper with viewable execution plans.</p>
<p>Members are matched on a set of name and type rules.&nbsp; If the type doesn&rsquo;t map, no extra config is required to prevent errors.</p>
<p>Execution plans are formed by creating an expression tree and compiling to a function, these are then cached.&nbsp; These plans can be cached upfront and also viewed. The execution plans can be validated during development.</p>
<p>The library also offers deep cloning, updating and merging amongst other useful features.</p>
<p>See <a title="AgileMapper" href="https://github.com/agileobjects/AgileMapper" target="_blank" rel="noopener">https://github.com/agileobjects/AgileMapper</a></p>
<h3><strong>Mapster</strong></h3>
<p>Mapster is another zero-configuration object-object mapper that matches by name.&nbsp; Once more, it supports configuration that configuration can be validated.</p>
<p>It uses an extension method which is available on any object to perform the mapping operation.&nbsp; Another extension method provides a convenient way of passing in values at runtime during mapping.</p>
<p>Mapster boasts good performance according to its own GitHub project page and compares itself with other libraries favourably.&nbsp;</p>
<p>The code repo also provides a tool to create DTO classes and extension methods from entity classes which can generate code and save time.</p>
<p><a title="Mapster" href="https://github.com/MapsterMapper/Mapster" target="_blank" rel="noopener">https://github.com/MapsterMapper/Mapster</a></p>
<h2><strong>How Do They Compare</strong></h2>
<h3><strong>Popularity</strong></h3>
<p>In terms of current usage AutoMapper is by far the most popular and established mapping library from the three selected.</p>
<table style="border-collapse: collapse; width: 87.8505%; height: 275px; border-color: #7e8c8d; border-style: solid;" border="2" cellpadding="5">
<tbody>
<tr style="height: 53px;">
<td style="width: 28.7567%; height: 53px;" width="189">
<p><strong>&nbsp;</strong></p>
</td>
<td style="width: 21.6138%; height: 53px; text-align: right;" width="142">
<p><strong>AgileMapper</strong></p>
</td>
<td style="width: 22.9816%; height: 53px; text-align: right;" width="151">
<p><strong>AutoMapper</strong></p>
</td>
<td style="width: 18.1184%; height: 53px; text-align: right;" width="119">
<p><strong>Mapster</strong></p>
</td>
</tr>
<tr style="height: 53px;">
<td style="width: 28.7567%; height: 53px;" width="189">
<p>NuGet Version</p>
</td>
<td style="width: 21.6138%; height: 53px; text-align: right;" width="142">
<p>1.8.0</p>
</td>
<td style="width: 22.9816%; height: 53px; text-align: right;" width="151">
<p>10.1.1</p>
</td>
<td style="width: 18.1184%; height: 53px; text-align: right;" width="119">
<p>7.2.0</p>
</td>
</tr>
<tr style="height: 53px;">
<td style="width: 28.7567%; height: 53px;" width="189">
<p>Total Downloads</p>
</td>
<td style="width: 21.6138%; height: 53px; text-align: right;" width="142">
<p>300k</p>
</td>
<td style="width: 22.9816%; height: 53px; text-align: right;" width="151">
<p>150M</p>
</td>
<td style="width: 18.1184%; height: 53px; text-align: right;" width="119">
<p>2.3M</p>
</td>
</tr>
<tr style="height: 53px;">
<td style="width: 28.7567%; height: 53px;" width="189">
<p>Daily Average Downloads</p>
</td>
<td style="width: 21.6138%; height: 53px; text-align: right;" width="142">
<p>100</p>
</td>
<td style="width: 22.9816%; height: 53px; text-align: right;" width="151">
<p>39k</p>
</td>
<td style="width: 18.1184%; height: 53px; text-align: right;" width="119">
<p>1k</p>
</td>
</tr>
<tr style="height: 53px;">
<td style="width: 28.7567%; height: 53px;" width="189">
<p>GitHub Stars</p>
</td>
<td style="width: 21.6138%; height: 53px; text-align: right;" width="142">
<p>375</p>
</td>
<td style="width: 22.9816%; height: 53px; text-align: right;" width="151">
<p>8k</p>
</td>
<td style="width: 18.1184%; height: 53px; text-align: right;" width="119">
<p>1.5k</p>
</td>
</tr>
</tbody>
</table>
<p>* correct as of 2021-05-17</p>
<h3><strong>Ease of use</strong></h3>
<p>All three were simple to understand and to setup using NuGet.&nbsp; In terms of usability, Agile Mapper and Mapster were easier than AutoMapper as neither have a requirement for configuration.&nbsp; &nbsp;</p>
<p>Getting started is similar for all three libraries:</p>
<ol>
<li>Install package</li>
<li>Have source and destination types to map</li>
<li>Create configuration (required for AutoMapper)</li>
<li>Create instance (required for AutoMapper)</li>
<li>Perform some mapping!</li>
</ol>
<p>Agile Mapper and Mapster both offer a static API, something that AutoMapper removed in v9.</p>
<p>Mapster is slightly different to the other two as it provides an extension method for the mapping operation. This is a convenient feature and feels natural to use.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="https://ivwebsitestorprod.blob.core.windows.net/iv-website-files/Map3_637569519320307972.png" alt="Basic use of each object mapper" width="551" height="185" /></p>
<h3><strong>Ease of configuration</strong></h3>
<p>Despite working by convention, mapping configuration is required in most cases. A trivial example included in the code example is to map a source first name and last name to a single destination full name field.&nbsp; This was a straightforward exercise in all three using fluent syntax.</p>
<p>All libraries are designed to work with dependency injection with configuration that is created once as a start-up operation.&nbsp; This will provide the application with a compiled set of mappings leading to a performance gain.</p>
<p>Configuration can be easily loaded as all the libraries provide a way of scanning for configuration classes, which is especially useful in larger codebases.</p>
<p>All three support operations before and after mapping takes place.&nbsp; AgileMapper offers some additional extension methods to limit when this should be applied which is a nice feature.</p>
<h3><strong>Collections</strong></h3>
<p>AgileMapper seems to support more collection types than AutoMapper or Mapster.&nbsp; Both AgileMapper and Mapster also have Dictionary support too, but only for keys with a string type.&nbsp;</p>
<p>During the mapping process, both AutoMapper and AgileMapper initialised empty collections when no value was to be mapped.&nbsp; &nbsp;This behaviour is good practice as collections are expected to be initialised. Mapster didn&rsquo;t do this and returned nulls as default behaviour.</p>
<h3><strong>Mapping and reverse mapping</strong></h3>
<p>All three libraries performed straightforward mapping based on matching names.&nbsp; They also performed reverse mapping, although AutoMapper required configuration for this to work.</p>
<p>Another test was to see the default behaviour for a mapping of a larger type into a smaller type, in&nbsp; this case an int to a byte.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="https://ivwebsitestorprod.blob.core.windows.net/iv-website-files/Map4_637569519321132640.png" alt="Difference between types of members with same name" width="719" height="237" /></p>
<p>All three libraries handled the situation differently.</p>
<ul>
<li>AutoMapper threw a runtime exception, so this mapping had to be explicitly ignored.</li>
<li>AgileMapper ignored the mapping as it has more complex mapping rules.</li>
<li>Mapster actually performed a mapping and added an incorrect value to the destination member. This is easily preventable using the config to ignore the member.</li>
</ul>
<p>As with everything, testing and exploring what is actually happening to data and validating configuration is essential.</p>
<h3><strong>Flattening</strong></h3>
<p>All three support flattening by convention without any special configuration when the destination names followed a simple pattern.</p>
<p>For example:&nbsp; <code>source.Author.StarRating</code> will map to <code>dest.AuthorStarRating.</code></p>
<h3><strong>Nested objects</strong></h3>
<p>All three supported mapping an object graph without any additional configuration.&nbsp; AgileMapper has a dedicated Flattening API with some useful functions such as flattening an object to a dictionary or query string parameter list.</p>
<p><strong>IQueryable Projections</strong></p>
<p>All three support IQueryable projections.&nbsp; This is a convenient way to map an entity object directly to the destination DTO when working with EF Core.</p>
<p>They are all very similar except for AutoMapper which requires the config to be passed into the extension method.</p>
<p><img style="display: block; margin-left: auto; margin-right: auto;" src="https://ivwebsitestorprod.blob.core.windows.net/iv-website-files/Map5_637569528648900897.png" alt="Projection usage for each object mapper" width="400" height="339" /></p>
<p>The projection ensured only the required members were declared in the select statement.&nbsp; &nbsp;The code example includes SQL logging so that it possible to inspect the SQL that was generated.&nbsp;</p>
<p>&nbsp;<img src="https://ivwebsitestorprod.blob.core.windows.net/iv-website-files/Map6_637569528651213672.png" alt="EF Core generated SQL" width="923" height="291" /></p>
<h3><strong>Documentation</strong></h3>
<p>Each library has documentation, but AutoMapper and AgileMapper have the clearest and most comprehensive content.&nbsp; Mapster could improve in this area, but hey, this is open source!</p>
<p><em><strong>A Note on Performance</strong></em></p>
<p>This study didn&rsquo;t aim to do a performance comparison.&nbsp; Mapster boasts of superior performance compared to AutoMapper due to its pre-compilation strategy. However, all the libraries work in a similar way using compiled expression trees which are then cached for future execution.</p>
<h2><strong>Conclusion</strong></h2>
<p>First of all, if you are writing manual object to object mapping code, then start using a mapping library!&nbsp; This will speed up your development leaving you with fewer lines of code to maintain.&nbsp; There is plenty of choice, but any of the three in this article are suitable candidates.</p>
<p>If you are looking for a quick way in, then out of these three AgileMapper gives you a no-fuss, zero-config start and you can start by using its static API.</p>
<p>After looking at AutoMapper, AgileMapper and Mapster, the conclusion is that they are all fairly similar.&nbsp; They all contain the key features required for object mapping tasks.&nbsp; There are subtle usage and configuration differences, but any of them should be fine in most cases.</p>
<p>AutoMapper&rsquo;s enforced configuration leads to a good habit to define the configuration upfront, but the lack of a static API removes some versatility available in the other two libraries.&nbsp;</p>
<p>Mapster&rsquo;s extension method approach is a natural way to map objects and their performance figures, if true, are a bonus, but there was an issue in the mapping that needed configuration to resolve.</p>
<p>AgileMapper seems to have a good set of rules for mapping with quality documentation and some useful features not available in the other libraries.</p>
<p>As mentioned previously, in all cases, take the time to validate and check the mapping configuration and be aware that convention-based mapping can lead to data errors in some cases.</p>
<p>These libraries are feature rich and this article has not covered everything they can do.&nbsp; If you want to find out more, then read the docs and feel free to use <a title="Mapping Demo project on GitHub" href="https://github.com/JD-Innovensa/MappingDemo" target="_blank" rel="noopener">the demo project!</a></p>
<p>&nbsp;</p>
<p><em>Thanks for reading this whistle-stop comparison of object mapping libraries.</em></p>