DEV Community

Fabrizio Bagalà
Fabrizio Bagalà

Posted on • Edited on

Object Mapping in .NET

Object mapping is a critical technique in software development that involves converting data from one object type into another. Essentially, it's about matching the properties of two different types of objects, so that their data can be seamlessly passed from one to another. This is particularly useful when you need to transfer data between objects that are not compatible or were designed to work with different architectures or data models.

There are several scenarios where object mapping is incredibly beneficial:

  • Data Transfer Objects (DTOs): When data is retrieved from a database, it's often transformed into DTOs which are plain objects that hold data. These DTOs can then be mapped to objects that have more business logic.
  • Database Abstraction: ORM is often used as a way to map the data retrieved from a database into objects within an application. This abstraction allows developers to interact with databases using object-oriented code rather than writing SQL queries.
  • Model Transformations: When working with different layers in an application, such as UI, business logic, and data access layers, the shape of the data often needs to change to suit the specific layer. Object mapping helps in transforming the data models as they move between layers.
  • API Responses: When developing APIs, you often want to control the shape of the data being sent to the client. Object mapping allows you to map your internal data models to a simplified model suitable for an API response.

The complexity of object mapping can range from simple, where properties are copied as is, to complex where some kind of transformation logic is applied during the mapping. The latter might include changing the type of the data, restructuring the object, or aggregating multiple properties into one.

Why use object mapping?

Using object mapping is beneficial for several reasons. First, it reduces the boilerplate code that is required when manually mapping fields from one object to another, which contributes to cleaner code. Secondly, object mapping enhances maintainability by centralizing the data model, so updates can be made in one place without having to modify the mapping logic throughout the entire application. Lastly, performance is an important aspect and many object mapping libraries are optimized to provide high-performance mapping solutions.

Popular libraries

When it comes to object mapping, several libraries have gained popularity among developers for their efficiency, ease of use and flexibility. Among them:

1️⃣ AutoMapper: It is perhaps the most well-known library for object mapping in .NET. It allows you to configure maps between objects in a very simple manner and is extremely flexible.

var configuration = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Person, PersonDto>();
});

var person = new Person
{
    Id = 1,
    FirstName = "John",
    LastName = "Doe",
    Birthday = DateTime.UtcNow
};
var mapper = configuration.CreateMapper();
var personDto = mapper.Map<PersonDto>(person);
Enter fullscreen mode Exit fullscreen mode

Here, we first create a MapperConfiguration object, which is used to configure the mappings. Within the configuration, we call CreateMap method to define a mapping between Person and PersonDto. After setting up the configuration, we create an instance of the mapper through CreateMapper method. Finally, we call the Map method on the mapper object to transform the source object into the destination type.

2️⃣ Mapster: It is known for its performance and ease of use. It offers a lightweight alternative to AutoMapper, and is just as powerful.

var person = new Person
{
    Id = 1,
    FirstName = "John",
    LastName = "Doe",
    Birthday = DateTime.UtcNow
};
var personDto = person.Adapt<PersonDto>();
Enter fullscreen mode Exit fullscreen mode

Mapster allows for a more streamlined syntax. You can simply call the Adapt method directly on the source object, and specify the type of the destination object as a generic parameter. Mapster automatically handles the mapping configuration and execution for you, making it very concise and easy to use.

3️⃣ ExpressMapper: Focused on speed, ExpressMapper is a lightweight object mapping library that relies on expression compilation to achieve high-performance mappings.

ExpressMapper.Mapper.Register<Person, PersonDto>();
var person = new Person
{
    Id = 1,
    FirstName = "John",
    LastName = "Doe",
    Birthday = DateTime.UtcNow
};
var personDto = ExpressMapper.Mapper.Map<Person, PersonDto>(person);
Enter fullscreen mode Exit fullscreen mode

In ExpressMapper, the mapping configuration is done through a static Mapper class. You call the Register method to define mappings between source and destination types. After that, you use the Map method on the same Mapper class to transform the source object into the destination type.

⚠️ Warning
ExpressMapper 1.9.1 does not support mapping between record.

Implicit operator

In addition to the libraries mentioned above, it is possible to map objects using implicit conversion operators. An implicit conversion operator allows for the conversion of an object of one custom type to another custom type without explicit casting. This provides a clean way to perform simple object mappings, particularly when the transformation logic is straightforward.

Here is an example illustrating how to define implicit conversion operators for object mapping:

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime Birthday { get; set; }
}

public class PersonDto
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime Birthday { get; set; }

    public static implicit operator PersonDto(Person person)
    {
        return new PersonDto
        {
            Id = person.Id,
            FirstName = person.FirstName,
            LastName = person.LastName,
            Birthday = person.Birthday
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, we have two classes, Person and PersonDto. The PersonDto class has a static implicit operator defined which takes an instance of the Person class as a parameter. Inside the operator, a new instance of the PersonDto class is created and the properties are mapped from the Person object.

You can use this implicit operator like this:

var person = new Person
{
    Id = 1,
    FirstName = "John",
    LastName = "Doe",
    Birthday = DateTime.UtcNow
};
PersonDto personDto = person;
Enter fullscreen mode Exit fullscreen mode

Notice that there is no explicit cast or function call necessary for the conversion. The implicit operator automatically handles the conversion and mapping of properties between the two objects. This can make your code cleaner and more readable, especially for simple mappings.

However, it is important to be cautious when using implicit operators, as they can sometimes lead to unexpected conversions which are not immediately obvious from the code. For complex mapping scenarios, using a dedicated mapping library may be more appropriate.

Benchmark

To quantify and compare the performance of the object mapping strategies discussed earlier, we can employ BenchmarkDotNet.

[Config(typeof(ObjectMappingBenchmarkConfig))]
public class ObjectMappingBenchmark
{
    private sealed class ObjectMappingBenchmarkConfig : ManualConfig
    {
        public ObjectMappingBenchmarkConfig()
        {
            SummaryStyle = BenchmarkDotNet.Reports.SummaryStyle.Default.WithRatioStyle(RatioStyle.Trend);
        }
    }

    private IMapper _mapper = null!;
    private Person _person = null!;

    [GlobalSetup]
    public void Setup()
    {
        var configuration = new MapperConfiguration(cfg =>
        {
            cfg.CreateMap<Person, PersonDto>();
        });
        _mapper = configuration.CreateMapper();

        ExpressMapper.Mapper.Register<Person, PersonDto>();

        _person = new Person
        {
            Id = 1,
            FirstName = "John",
            LastName = "Doe",
            Birthday = DateTime.UtcNow
        };
    }

    [Benchmark(Baseline = true)]
    public void AutoMapper()
    {
        _ = _mapper.Map<PersonDto>(_person);
    }

    [Benchmark]
    public void Mapster()
    {
        _ = _person.Adapt<PersonDto>();
    }

    [Benchmark(Description = "ExpressMapper")]
    public void Expressmapper()
    {
        _ = ExpressMapper.Mapper.Map<Person, PersonDto>(_person);
    }

    [Benchmark]
    public void ImplicitOperator()
    {
        PersonDto personDto = _person;
    }
}
Enter fullscreen mode Exit fullscreen mode

From the comparison, the following results were obtained:

|           Method |      Mean |     Error |    StdDev |        Ratio | RatioSD |
|----------------- |----------:|----------:|----------:|-------------:|--------:|
|       AutoMapper | 51.673 ns | 0.0744 ns | 0.0659 ns |     baseline |         |
|          Mapster | 26.851 ns | 0.0688 ns | 0.0644 ns | 1.92x faster |   0.00x |
|    ExpressMapper | 70.970 ns | 0.3536 ns | 0.3307 ns | 1.37x slower |   0.01x |
| ImplicitOperator |  6.926 ns | 0.0493 ns | 0.0461 ns | 7.46x faster |   0.06x |
Enter fullscreen mode Exit fullscreen mode

The table reflects a comparison of how swiftly different object mapping techniques can execute the mapping process. With the ImplicitOperator method clearly coming out on top, it’s evident that this method has been optimized for performance, likely employing minimal overhead. This is essential in high-throughput scenarios where object mapping needs to be rapid and efficient, such as real-time data processing or applications with high transaction rates.

Mapster, which follows ImplicitOperator in performance, demonstrates that it's a solid choice for those who are looking for a balance between ease of use and performance. Though not as fast as using the ImplicitOperator, Mapster offers additional features and flexibility which may be more suited to varied and complex mapping scenarios.

AutoMapper, being in the mid-range, is known for its rich set of features and versatility, but this comes at the cost of performance. It is a well-established library and might be preferred in applications where the mapping requirements are complex and the slight lag in performance is not a critical issue.

ExpressMapper lags behind the rest in terms of performance. This indicates that, while it might have its own set of features that make it useful in certain contexts, it is not the best choice when performance is paramount. ExpressMapper might be more applicable in scenarios where the ease of configuration and specific feature set align with the project requirements.

⚠️ Warning
Keep in mind that you may get different values than those given in the table. This depends on various factors, such as the complexity of the object to be mapped, the hardware of the machine on which the code is run, etc.

Conclusion

Object Mapping is a gear that facilitates the movement of data through the various layers and components of an application. With a variety of tools and techniques at their disposal, such as AutoMapper, Mapster, ExpressMapper, and implicit operators, .NET developers have several options for implementing efficient and maintainable mappings. The choice of the appropriate method will depend on the specific requirements of the project and the complexity of the mappings.

References

Top comments (2)

Collapse
 
xaberue profile image
Xavier Abelaira Rueda

Hi @fabriziobagala ! Nice article, thanks for sharing as all your nice stuff!

It's worth mentioning also that Mapster provides an option where allow the project to have code-gen mappers automatically cretated providing almost the same performance than manual mappers.

In addition, another manual mapping options are using specific services, extension methods or in specific cases, from Entity to DTOs using DTOs constructors.

Thanks again for your article 👌

Collapse
 
fabriziobagala profile image
Fabrizio Bagalà

Hi @xelit3
Thank you so much for your support 🙏

You are right! Mapster allows you to have code-gen mappers automatically created providing superior performance to the "normal" version. This is possible because of the new Mapster Tool.

I did not list all the methods to map objects just because I focused on the most used methods. However, as you wrote it is possible to map objects in several ways.

If I can carve out some time, I would like to add the Mapster (Codegen) in the comparison so as to make developers aware of using this interesting tool 😉