.NET: mapping library in 2024

Mark Shv
6 min readSep 24, 2024

--

Preview image generated by v0

As we know, there are 2 Common Approaches to Model/DTO Mapping

  1. Manual mapping — the developer fully manages/controls the process of data transfer between models
  2. Auto-mapping — the developer uses a ready-made library that does the mapping automatically according to the developer’s instructions

Drawbacks of Manual Mapping

  • Time spent on mapping essentially identical fields in models
  • A lot of control is needed everywhere to check what you forgot to map — the compiler doesn’t prompt, you need to cover such cases with tests. You can probably use generation feature of IDE to create mapping between two models but the adding to/editing of existing mappings requires a lot of attentiveness

Issues with AutoMapper

For the second approach in .NET, the most popular library, AutoMapper, is usually used. It seems fine, you don’t have to think about field control as with manual mapping, you add to both models and it’s automatically mapped. But not everything is good here either:

  • I’m not shown where my fields in the model are used, because the mapping happens in the depths of AutoMapper, so we learn about problems at runtime or by calling AssertConfigurationIsValid at application startup, or in unit tests
  • I need to inject AutoMapper into services (classes) using the IMapper interface. And still, to find all the places in the code where my models are mapped, I need to search in the IDE for something like Map<SomeDto> or Map<List<SomeDto>>, etc.
  • With AutoMapper, bugs with mapping were observed on projects directly in environments, especially bugs related to nullable fields. If nullable fields weren’t handled properly somewhere, that’s it. Example: requested the first 100 elements by pagination — it works, but on the next page — an error (c# object reference not set to an instance).

As communication with colleagues on projects shows, distrust in the mapping library is the reason for continuing to use manual mapping in projects.

Introducing Mapperly

There is a better alternative — a mapping library based on source generators And the name of this library is Mapperly (https://github.com/riok/mapperly)

This library solves the problems of AutoMapper and doesn’t exclude the use of manual mapping if you really want to :)

Advantages of Mapperly:

  • Ease of use
  • Ability to find where a field is used in mapping, thanks to source generators
  • Faster performance, again, thanks to source generators
  • You can combine both auto-mapping and manual mapping within one model
  • More intelligently able to map types automatically
  • No need to inject anything into classes

Setting Up Mapperly

  1. Install the Riok.Mapperly package
dotnet add package Riok.Mapperly

2. Create a class (preferably static) and set the [Mapper] attribute on it

Screen 1

3. We write a method as an extension to our model:

Screen 2

4. We specify with attributes additionally what needs to be mapped if the fields don’t match by name

Screen 3

In AutoMapper, the same would look like this:

Screen 4

Notice, I had to help AutoMapper to map datetime->dateonly, but Mapperly can do this. Let’s look at the generated code:

Screen 5

Now you can go to the method implementation in the code through the Go to Declaration or Usages call, or go through the solution explorer as in the screenshot on the left.

And most importantly, what we got, we can now see where our fields are used in both models

Screen 6
Screen 7

And we also have the ability to find where this method is used in the code:

Screen 8

Let’s now try to consider more custom scenarios:

  1. Ignore a specific field:
Screen 9

2. Apply custom mapping to a separate field:

Screen 10

IMPORTANT: MapToVisitsSummary uses => lambda. This is important for the mapping translation to work in a DB query. Always use lambda in Mapperly, not a return statement.

In AutoMapper, we would implement the example like this:

Screen 11

3. And how does Projection work there, you ask? It works fine, but you need to write one more method, which, by the way, is easier to track later:

Screen 12

This code is generated:

Screen 13

on line 49, due to that => lambda, the linq is built correctly

4. Let’s try to combine auto-mapping with manual mapping

Screen 14

Generated code:

Screen 15

5. Mapping using base models

Suppose we introduce a base model for entity: two matching fields Id and UserId + isDeleted field

Screen 16

And we introduce a base model for Dto with a custom Status field

Screen 17

We inherited our test model from these base ones:

Screen 18

And now we want to make the mapping continue to work. In AutoMapper, we need to give instructions on the base model:

Screen 19

In Mapperly, this case looks more complicated, in this aspect it is inferior to AutoMapper:

Screen 20

You have to specify for each inherited type how to map the custom Status field in BaseDto. The Id and UserId fields mapped without problems. For now, it works like this, there’s an open ticket in the library’s GitHub repository to improve work with derived types. Despite this drawback, it’s easier to keep track of unmapped fields in Mapperly than in AutoMapper, and even easier than with manual mapping

This is achieved by adding lines to the .editorconfig file of the project:

[*.cs]
dotnet_diagnostic.RMG012.severity = error # Unmapped target member

Now, if I add a new field in Dto with a name or type that Mapperly can’t map on the fly, I’ll get an error at the project compilation stage:

Actually, this feature will allow you not to have problems with the fact that you didn’t keep track of mapping somewhere when adding new fields to the model.

That’s all:) I hope the information was useful, and you will try to use it in your projects!

P.S. Code from this article here:

ifmelate/MappingDemo: .NET: Testing Mapperly vs Automapper (github.com)

--

--