Marcelo Daniel Toledo

Marcelo Daniel Toledo

Software Developer | Bachelor's Degree in Computer Science

Back to Articles
#1 Apr 21, 2026

AutoMapper, your lesson cost me dearly

Author

Marcelo Daniel Toledo

#.NET #Anecdote #Refactor #Dependencies
4 min read
Link copied!

AutoMapper, your lesson cost me dearly

The other day a developer showed me a library that generated incredible dashboards for React and asked if he should implement it. The charts were beautiful, the animations fluid, everything very polished. But the first thing I did was go straight to the license section. Sure enough, it offered several plans. I explained how expensive it can be to add an external dependency for something that, with a bit of effort, you can solve yourself.

The conversation ended and I kept thinking. Not about React or dashboards, but about that: about normalizing what bothers us because it “works.” I have several production projects that have been throwing AutoMapper warnings for years.

I think it happened to all of us who’ve been in the .NET ecosystem for a while — you almost certainly know (and used) libraries like AutoMapper, MediatR, etc. They were the go-to pair for any “Improve your mappings in .NET with this tool” or “Implement CQRS the easy way” guide. Well, some time ago their author decided to move them to commercial licensing, which I think is fair and isn’t the main point of this article.

This got me thinking about something we know but sometimes overlook due to time constraints: external dependencies. For mappings, maybe it was justified a few years ago — nobody enjoyed writing thousands of manual mappings for complex objects. But it’s 2026, where an LLM generates those mappings in seconds. The “it’s too tedious to do by hand” argument no longer holds.

Replace one nail… with another nail?

My first instinct was to look for alternatives and benchmarks. Because of course, you always look for that new, better library, right?

Automapper vs Manual Assignment

Well, in this case it was time to stop and think. If the wheel is simple, I don’t mind reinventing it if we reduce the risk of an external dependency. Manual mappings aren’t as complex as they sound — you don’t need another library to map from A to B. And with generative AI, tasks that used to take hours are solved in minutes. The same technology we use to solve code removes the excuse for depending on another library for something this basic.

Ok, manual mappings. What about maintenance?

“Initial development happens once, maintenance is forever” — this is where the main fear surfaces whenever we need to make a change: what if I add a property and forget to map it? Let’s handle that with reflection-based tests!

using System.Reflection;
using FluentAssertions;
using Xunit;

public class MappingTests
{
    [Fact]
    public void UserDto_ShouldHaveAllPropertiesFrom_User()
    {
        // 1. Define the types we want to compare
        var entityType = typeof(User);
        var dtoType = typeof(UserDto);
        // 2. Get the Entity (Source) properties
        var entityProperties = entityType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                         .Select(p => p.Name)
                                         .ToList();
        // 3. Get the DTO (Destination) properties
        var dtoProperties = dtoType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                                   .Select(p => p.Name)
                                   .ToList();
        // 4. Compare (here: DTO must contain everything from the Entity)
        // If you add "Age" to the Entity and forget the DTO, the test fails.
        dtoProperties.Should().Contain(entityProperties,
            "because the DTO must stay in sync with the domain entity");
    }
}

If there are fields you know shouldn’t be in the DTO, create an exclusion list to filter them out:

var excludeList = new[] { "PasswordHash", "SecretKey" };
var filteredEntityProps = entityProperties.Except(excludeList);

Done — we’ve ensured we won’t make mistakes during maintenance. Now let’s get to the actual replacement.

My plan

And here I decided not to repeat the same mistake with another tool.

With a product in production and the number of mappings the project had, it wasn’t the time to throw code at the wall and see what sticks. I’ve been using Spec-Driven Development for a while and I’m comfortable with it — so first: document everything. Which mappings existed, what behavior had to be preserved, what couldn’t break. Then, and only then, code.

The plan was:

  1. Audit all existing mappings (Profiles + static)
  2. Classify them by category (Domain↔DTO, External Service, etc.)
  3. Identify and plan resolution of architectural violations
  4. Per use case (leveraging the project’s structure): a. Write tests that capture the CURRENT behavior b. Implement the manual mapping c. Remove the corresponding Profile d. Verify the tests pass
  5. Finally: remove AutoMapper from the project
PR: remove automapper

What surprised me most wasn’t the number of mappings, but what was inside them. Some profiles were hiding business logic that had no business being there. Business rules disguised as mappings, that nobody reviewed because “AutoMapper handles it anyway.” AutoMapper was convenient, yes. But it was also a very good place to hide things.

Oh, I almost forgot!

Whatever happened to the dashboard developer? He ended up implementing the library. He messaged me a month later: they had changed the license and now required an enterprise plan. No, I’m kidding. Well, not really.