Why I don’t like AutoMapper

8th October 2024


I was recently required to do some refactoring work inside a large-ish .NET project that involved modularising part of the codebase.

As part of the discussion around this bit of work, the topic of AutoMapper came up, we have used AutoMapper in some of our other projects and having had to spend my time dealing with integrating AutoMapper into these projects as well as fixing the problems that it creates I have come to strongly dislike AutoMapper.

I have several reasons why I don’t like AutoMapper but it basically boils down to two points;

What is AutoMapper

if you’re new to .NET there is a good chance you haven’t heard of AutoMapper. It is one of the most recognised and used third party packages having been around for over 10 years with 689.2 million downloads (as of writing) from nuget.org. Pretty incredible stats by any measure, even the .NET Foundation has seen fit to recognise and sponsor AutoMapper.

AutoMapper’s prime directive is to simplify the process of mapping from one type to another, something that I’m sure everyone who has worked with a strongly typed language before has no doubt had to do.

Working in .NET is great, C# is a fantastically rich programming language and there are some truly wonderful open source .NET projects out there, no wonder C# / .NET sits high on the list of most popular languages. With that in mind however, I find myself asking “Is AutoMapper even necessary?”

The AutoMapper Problem

As previously stated, AutoMapper’s goal is to rid you of the tedious task of writing code that just simply maps one object to another.

How AutoMapper accomplishes this is via the use of expression trees and reflection which is abstracted away from you and instead you are given an IMapper interface which you can use as such…

var sourceObject = new SourceType();
var destinationObject = mapper.Map<DestinationDto>(sourceObject);

Here we have mapped an object from it’s source type to a DTO in two (technically one) lines of code, it’s nice and neat, it is obvious what the code is doing and you haven’t had to waste your time having to manually writing code that explicitly maps each property, everything is abstracted away from you, and this is precisely my issue.

Your DTOs are important! treat them as such

Believe it or not, DTOs are much more important than developers give them credit for, be they for transferring data between layers inside your application or as a API response. DTOs are supposed to be a 100% true and accurate depiction of the source object they are mapped from, so why are you abstracting away the single most important bit of logic a DTO can have?!

I’m speaking from experience here, I have spend more time than I care to admit debugging AutoMapper issues, from missing properties, data being mapped in unexpected ways and all the additional complexities that come from having yet another service registered with your DI container (more on this later).

Nearly all of these issues were (thankfully) found at runtime on our test environments, yet they could have caused any number of potential issues for any API or project that consumes the DTOs and all because we, as developers have decided to surrender explicitly mapping our domain objects to DTOs in order to save a small amount of development time.

Abstraction leads to duplication

It is inevitable that eventually someone else will end up maintaining your code. By abstracting away mapping logic your DTOs are now out of sight and out of mind, a fresh developer coming in will not always be aware of the abstractions you have made either through your own code or through the use of third party libraries.

I have found in some of our projects multiple DTOs for a single domain object that are almost identical (minus one or two properties) because a developer has made an assumption and now we have ended up with a single domain object that has multiple DTOs representing it all with slightly different names. Given enough time and developers your projects will eventually start to reach a critical mass where developers are not sure if it is safe to reuse or edit existing DTOs so will create their own further compounding the problem.

Test your mappings!

There seems to be this assumption that by palming something off to a third party library this means that you do not have to write tests. As mentioned, your DTOs are important so give them the tests they deserve, check that each and every property maps the way you expect with a wide variety of data, do not just blindly trust that AutoMapper will do your job for you.

The Wider Problem

‘Clean code’ in a nutshell

I think it’s fair to say that there is a well documented tendency among .NET / OOP developers to over engineer simple solutions.

As an example, searching for “dotnet clean architecture” will give you a solution template that contains 9 different projects (5 of which are purely for tests), 20+ nuget packages, a DI container, a local test database and custom middleware all for you to configure and setup before you even think of writing a single line of business logic.

At what point should we really step back and ask “Am I putting the ICartFactory before the IHorseProvider?”

For me, AutoMapper really sums up this sort of thinking, A third party package that you need to install, configure and manage all so you can save yourself a job that could be achieved in a few lines of code.

As you can probably tell by this website’s design, I enjoy simplicity, yet simplicity and brevity are two different things. For me, twenty lines of explicit mapping code with zero dependencies will always be simpler than than two lines of AutoMapper code.

A Better Approach

I suppose a more fitting title for this post should be “Why I don’t like (the way certain people use) AutoMapper”

AutoMapper is a package that provides a powerful tool that gets abused by developers. If your domain objects are highly complex and nested and you wish to flatten and map them to a simple DTO, then AutoMapper could make sense, everything outside of this specific scenario however less so.

Developers are naturally lazy, when given the choice between a boring repetitive task and a quick and easy shortcut then a good chunk of people will choose the second option but it is precisely this laziness that compromises quality and accuracy.

Anyway, rather than continue to talk about AutoMapper I will instead move on to quickly talk about what is now my approach going forward, taking into account everything I have talked about so far, I have come up with a set of rules that I like to stick to;

Here is an example of what this might look like in practise

// Our DTO
public record EmployeeDto
{
    public required long Id { get; init; }
    public required Guid UniqueId { get; init; }
    public required string LastName { get; init; }
    public required string FirstName { get; init; }

    public required DepartmentDto? Department { get; init; }
}

// Our domain object
public class Employee
{
    public long Id { get; private set; }
    public Guid UniqueId { get; private set; }
    public string LastName { get; private set; }
    public string FirstName { get; private set; }

    public long? DepartmentId { get; private set; }
    public Department Department { get; private set; }


    public EmployeeDto ToDto()
    {
        if (DepartmentId is not null && Department is null)
            throw new NullReferenceException($"Department must be loaded in order to map Employee");

        return new Employee
        {
            Id = Id,
            UniqueId = UniqueId,
            LastName = LastName,
            FirstName = FirstName,
            Department = Department?.ToDto()
        };
    }
}

I’m sure there are those out there who would disagree that this is preferable to AutoMapper and I will concede that this is by no means perfect but let us look at what it gives us.

We have a single DTO and a single point where the mapping operation tasks place. The DTO tells us which properties can be null and that we can expect all of these properties to be populated, this code is very testable, it is obvious what it does and any developer coming into your project will see this and know exactly what it does. We have accomplished our goal of mapping from one object to another without installing any third party packages or registering and dependencies with our DI container, and perhaps best of all; it is simple.

Anyway, I have rambled on long enough.

See you next time.