Stop Over Engineering Your DTO Mapping in .NET

In my earlier projects, I always reached for third-party libraries whenever I needed to map domain models to DTOs. It felt like the standard approach. Everyone around me was using some form of object mapper, so naturally, I followed suit. And for a while, it worked or at least, I thought it did.

But over time, I began to notice the small cracks.

There were moments when mappings failed silently. Other times, the behavior was unexpected because of some convention based mapping I wasn’t even aware was happening. Then came the prerequisites: configuring profiles, managing dependencies, injecting services, and dealing with unit test failures tied to mapping configuration.

For what?

A simple task like mapping a domain object with just three properties:

C#
// Customer Domain Model
public class Customer
{
    public CustomerId Id { get; set; }
    public Email Email { get; set; }
    public GitHubUsername GitHubUsername { get; set; }
}

// CustomerDTO
public class CustomerDTO
{
    public Guid Id { get; set; }
    public string Email { get; set; }
    public string GitHubUsername { get; set; }
}

I just wanted to map:

  • Customer.Id.Value to CustomerDTO.Id
  • Customer.Email.Value to CustomerDTO.Email
  • Customer.GitHubUsername.Value to CustomerDTO.GitHubUsername

That’s it. No transformation, no nesting. Just a pass-through.

My Journey Through Mapping Libraries

The first library I used was AutoMapper. It felt like a blessing at first, automatic mappings, minimal boilerplate, and no need to write line-by-line code. But eventually, I ran into its hidden complexity. The silent failures, the reliance on naming conventions, the necessity of setting up profiles they all added friction. There were times I spent more time debugging AutoMapper than writing the actual features I was assigned to.

Looking for a simpler, more modern alternative, I discovered Mapperly. It uses source generators instead of reflection, which felt more elegant and safe at compile-time. I liked that it had no runtime cost and was generally more transparent. In many ways, it improved on the shortcomings of AutoMapper.

But as I dug deeper and started customizing, I hit some roadblocks. I needed to use attributes, set up partial methods, and occasionally it failed when I upgraded my SDK version. At that point, I realized I was still at the mercy of the tooling.

So I Built My Own Mapper

I stepped back and thought: if I can write the mapping logic in less than 3 minutes, why am I even outsourcing this to a library?

Here’s what I ended up doing:

C#
public static class DomainToDtoMapper
{
    public static CustomerDTO ToCustomerDTO(this Customer customer)
    {
        return new CustomerDTO
        {
            Id = customer.Id.Value,
            Email = customer.Email.Value,
            GitHubUsername = customer.GitHubUsername.Value
        };
    }
}

Usage:

C#
var customerDTO = customer.ToCustomerDto();
return await _customerRepository.CreateAsync(customerDTO);

No magic. No config. No attributes. Just pure C# that I can read, debug, and trust.

Benefits I Gained from Going Manual

  • Clarity and Control: I know exactly what’s being mapped. No surprise behavior.
  • Better Debugging: If something breaks, I just open the method. There’s no abstraction hiding the logic.
  • Minimal Overhead: One less NuGet package. One less tool in the CI/CD pipeline.
  • Flexibility: Need to add a conditional mapping? Easy. Want to log something? Done. No need to dig through documentation.

Real-World Validation

I’ve worked on several projects using this manual mapping approach, and honestly, I never had any issues. Yes, it adds me an extra few minutes of coding, but in exchange, it saves me hours of debugging caused by the unexpected behavior of third-party libraries. And for me, that trade-off is more than worth it.

When Manual Mapping Is Enough and When You Need to Use a Library

The following is based on my personal experience and observations. What worked for me may not necessarily apply to every team or project. Always evaluate based on your actual use case and context.

When Manual Mapping Is Enough

If you’re dealing with simple, straightforward mappings like:

  • Domain-to-DTO conversions
  • Flat objects without deep nesting

Then honestly, you probably don’t need AutoMapper, Mapperly, or any other mapping tool.

When You Might Still Use a Library

I’m not here to bash mapping libraries. They have their place. If you’re working on a large system with:

  • Dozens of models
  • Complex transformation logic
  • High-pressure deadlines

Then yes, a mapping library might help. But use it with your eyes open. Know the trade-offs.

Final Thoughts

As developers, it’s tempting to reach for tools because they promise speed and abstraction. But sometimes that abstraction becomes the very thing that slows us down.

Everything I’ve shared here is based on my personal experience. Maybe your experience is different and that’s perfectly valid. If you haven’t used any mapping library yet and are planning to try one, there’s nothing wrong with that. Just consider some of the things I mentioned here. It might save you from unexpected frustrations.

At the end of the day, it all depends on your project’s needs and how well-versed you are with third-party libraries. For me, going back to manual mapping wasn’t a regression, it was a liberation.

Simple task. Simple solution.

References & Experience

If you’re struggling with mapping libraries, maybe the answer isn’t more tools. Maybe it’s just less noise.

Assi Arai
Assi Arai
Articles: 31