LEARN BUILD GROW TOGETHER
DEVELOPER LEARNING HUB
Learn. Build. Grow Together.

Practical tutorials, real-world examples and interview preparation.

.NETC#SQL SERVERANGULAR
Home /.NET 10 /AutoMapper in .NET 10: Complete Developer Learning Guide

AutoMapper in .NET 10: Complete Developer Learning Guide

November 15, 2026 5 min read , , , , , ,

AutoMapper: Complete Object Mapping Learning Guide for .NET Developers

Learn object-to-object mapping from fundamentals to advanced application design: DTOs, profiles, conventions, custom member mapping, nested objects, collections, dependency injection, projection, validation, testing, and production practices using C# 14, .NET 10, and AutoMapper 17.

Introduction

Modern applications rarely use one class to represent data at every layer. A database entity, an API request, an API response, and a domain model often represent the same business concept in different ways. Each type has a different responsibility, and moving information between them is a routine part of application development.

For example, an ASP.NET Core API may retrieve an Employee entity from a database but should return an EmployeeResponse DTO to the frontend. The entity may contain internal fields such as audit information or navigation properties that should not be exposed in an API response. The API request may contain only the fields a user is allowed to submit.

AutoMapper is a convention-based object-to-object mapping library for .NET. It helps transfer data between objects by matching compatible members and applying mapping rules that you configure. It is especially useful when mapping domain or persistence models to DTOs and other application-facing models.

This guide focuses on C# and ASP.NET Core development, but the underlying ideas—separating models, defining mapping rules, controlling data exposure, and validating transformations—are useful in other languages and application frameworks too.

We will use an Employee Management application as a running example. It contains employees and departments, with API request and response models, database entities, and business-facing types.

Learning approach: We will begin with why object mapping exists, then learn AutoMapper conventions and configuration. After that, we will build practical mappings, integrate them into an ASP.NET Core Web API, explore projection and advanced customization, and finish with testing, performance, security, and design guidance.
Version note: Examples target .NET 10 and C# 14 with AutoMapper 17.0.0, the latest stable release verified on November 15, 2026. AutoMapper 17 targets .NET 10 and .NET 11 (as well as specified compatibility targets); .NET 10 is therefore a supported target. AutoMapper licensing is also covered in this guide—do not skip that section. Verify package details on NuGet.

What Is Object Mapping?

Object mapping is the process of transferring data from one object to another. The source and destination objects may represent the same business information but have different structures, names, or responsibilities.

Imagine an employee record stored in a database. The database entity may include an internal identifier, audit columns, salary details, and a navigation property to a department. A public API response may need only the employee's display name, email address, and department name.

Simple Example

public sealed class Employee
{
    public int EmployeeId { get; set; }
    public string FirstName { get; set; } = "";
    public string LastName { get; set; } = "";
    public string Email { get; set; } = "";
}

public sealed class EmployeeResponse
{
    public int EmployeeId { get; set; }
    public string FirstName { get; set; } = "";
    public string LastName { get; set; } = "";
    public string Email { get; set; } = "";
}

Both classes contain similar information. Mapping copies the relevant values from an instance of Employee into an instance of EmployeeResponse.

Remember: Mapping is about transferring data between types. It does not automatically validate business rules, authorize a user, save data to a database, or decide which fields a user is allowed to change.

Why Do Applications Need Different Models?

Using one class for database storage, business logic, and API communication may look convenient initially, but it couples layers that should be allowed to evolve independently.

Common Model Types

ModelResponsibilityExample
EntityRepresents persisted data and relationships.Employee
DTOTransfers data between application boundaries.EmployeeResponse
Request modelRepresents data accepted from a client.CreateEmployeeRequest
View modelShapes data for a particular user interface.EmployeeListItem
Domain modelRepresents business concepts and behavior.EmployeeAggregate

Why Keep Them Separate?

  • Security: Prevent internal fields from being exposed accidentally.
  • API stability: Change database structure without automatically changing public contracts.
  • Validation: Apply request-specific validation without putting API concerns on persistence entities.
  • Maintainability: Keep responsibilities clear across application layers.
  • Flexibility: Return different shapes for details, lists, reports, and other use cases.
Database EntityEmployee Mapping LayerAutoMapper API ResponseEmployeeResponse Request DTOCreateEmployeeRequest
Mapping connects models without forcing every application layer to share one class.

What Is AutoMapper?

AutoMapper is a convention-based object-to-object mapper for .NET. It uses a fluent configuration API to define how one type maps to another. When source and destination member names and types match, AutoMapper can map many values by convention. You add explicit rules for differences or special behavior.

What AutoMapper Helps With

  • Mapping entities to DTOs and response models.
  • Mapping request models to application or command models.
  • Flattening nested object structures.
  • Mapping collections of objects.
  • Applying custom rules for individual members.
  • Projecting query results into DTOs when used with a compatible LINQ provider.

What AutoMapper Does Not Do

  • It does not replace domain modeling or application architecture.
  • It does not validate incoming requests automatically.
  • It does not authorize access to sensitive destination members.
  • It does not persist mapped objects to a database.
  • It does not make a poor or unclear model design good by itself.
Simple definition: AutoMapper reduces repetitive object-copying code by applying configured mapping rules between .NET types.

How AutoMapper Works

AutoMapper follows a straightforward lifecycle. You configure the mappings, register the configuration with the application, inject an IMapper where it is needed, and ask it to map a source object to a destination type.

1. Source TypeEmployee 2. ProfileCreateMap rules 3. DestinationEmployeeResponse 4. IMapper.MapExecutes mapping
Profiles describe the mapping; IMapper executes it for objects at runtime.
  1. Define source and destination types. For example, Employee and EmployeeResponse.
  2. Create a mapping configuration. Use CreateMap<TSource, TDestination>() inside a profile.
  3. Register AutoMapper. Add it to dependency injection and scan the assembly containing your profiles.
  4. Inject IMapper. Use the mapper in the application component that owns the transformation.
  5. Execute the mapping. Call Map<TDestination>(source).

Manual Mapping vs AutoMapper

Manual mapping means assigning each destination property yourself. It is explicit, easy to debug, and requires no mapping library. AutoMapper can reduce repetitive assignments when many mappings follow predictable conventions.

Manual Mapping

var response = new EmployeeResponse
{
    EmployeeId = employee.EmployeeId,
    FirstName = employee.FirstName,
    LastName = employee.LastName,
    Email = employee.Email
};

AutoMapper Mapping

var response = mapper.Map<EmployeeResponse>(employee);
ConsiderationManual MappingAutoMapper
ControlExplicit assignment in code.Centralized mapping configuration.
BoilerplateMore repetitive for large models.Less repetitive for convention-based maps.
DebuggingDirect and straightforward.Requires understanding profile rules.
Best fitSmall, special, or business-sensitive transformations.Repeated, predictable object transformations.
Runtime behaviorVisible in the assignment code.Depends on configured mapping rules.
Do not automate every assignment: For a small mapping or one containing important business decisions, manual code may be clearer. Use AutoMapper when it improves readability and reduces genuine repetition—not simply because a library is available.

Project Setup with .NET 10

Prerequisites

  • .NET 10 SDK.
  • Visual Studio 2026 or a compatible editor.
  • Basic C# knowledge, including classes, properties, generics, and dependency injection.
  • Basic understanding of DTOs and ASP.NET Core Web APIs.

Create a Web API

dotnet new webapi -n EmployeeApi
cd EmployeeApi

Install AutoMapper 17.0.0

dotnet add package AutoMapper --version 17.0.0

AutoMapper's dependency-injection registration is included in the core package in modern versions. Do not add the old AutoMapper.Extensions.Microsoft.DependencyInjection package to a new project just to obtain AddAutoMapper.

Project File

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="AutoMapper" Version="17.0.0" />
  </ItemGroup>
</Project>

Suggested Folder Structure

EmployeeApi/
|
+-- Controllers/
|   +-- EmployeesController.cs
|
+-- Entities/
|   +-- Employee.cs
|   +-- Department.cs
|
+-- DTOs/
|   +-- CreateEmployeeRequest.cs
|   +-- UpdateEmployeeRequest.cs
|   +-- EmployeeResponse.cs
|
+-- Mapping/
|   +-- EmployeeProfile.cs
|
+-- Services/
|   +-- EmployeeService.cs
|
+-- Repositories/
|   +-- EmployeeRepository.cs
|
+-- Program.cs
+-- appsettings.json

This is one possible structure. Organize profiles by feature or bounded context as the application grows. Avoid creating a single enormous profile file for every mapping in the solution.

Your First AutoMapper Mapping

Let's create a source class and a destination DTO. The properties have matching names and compatible types, so AutoMapper can map them using conventions.

Source Entity

namespace EmployeeApi.Entities;

public sealed class Employee
{
    public int EmployeeId { get; set; }
    public string FirstName { get; set; } = "";
    public string LastName { get; set; } = "";
    public string Email { get; set; } = "";
    public decimal Salary { get; set; }
}

Destination DTO

namespace EmployeeApi.DTOs;

public sealed class EmployeeResponse
{
    public int EmployeeId { get; set; }
    public string FirstName { get; set; } = "";
    public string LastName { get; set; } = "";
    public string Email { get; set; } = "";
    public decimal Salary { get; set; }
}

Create a Profile

using AutoMapper;
using EmployeeApi.DTOs;
using EmployeeApi.Entities;

namespace EmployeeApi.Mapping;

public sealed class EmployeeProfile : Profile
{
    public EmployeeProfile()
    {
        CreateMap<Employee, EmployeeResponse>();
    }
}

The Profile class is the place to group related mapping configurations. The CreateMap call defines the source and destination type pair.

Register the Profile

builder.Services.AddAutoMapper(
    typeof(EmployeeProfile));

Use IMapper

public sealed class EmployeeService(IMapper mapper)
{
    public EmployeeResponse ToResponse(Employee employee)
    {
        return mapper.Map<EmployeeResponse>(employee);
    }
}

When you call Map, AutoMapper creates the destination object and copies the matching values according to the configured rules.

Understanding Mapping Conventions

AutoMapper uses conventions to reduce the amount of configuration required. The most important convention is matching source and destination member names, with compatible types.

Same Property Names

public sealed class Employee
{
    public string FirstName { get; set; } = "";
    public string Email { get; set; } = "";
}

public sealed class EmployeeDto
{
    public string FirstName { get; set; } = "";
    public string Email { get; set; } = "";
}

These properties map by convention. You do not need to configure each member separately.

Different Property Names

If the source has FirstName but the destination has GivenName, configure that difference explicitly with ForMember.

Type Compatibility

AutoMapper can handle many straightforward conversions, but do not assume every type conversion is appropriate. Conversions involving dates, currencies, identifiers, enums, or culture-specific text should be designed and tested deliberately.

Remember: Convention-based mapping is convenient, but it is not magic. Understand which members are matched, which are ignored, and which require explicit configuration.

Creating and Organizing Profiles

A profile groups mapping definitions. Keeping mappings near the feature or model boundary they belong to makes them easier to find, review, and test.

One Profile for a Feature

public sealed class EmployeeProfile : Profile
{
    public EmployeeProfile()
    {
        CreateMap<Employee, EmployeeResponse>();
        CreateMap<CreateEmployeeRequest, Employee>();
        CreateMap<UpdateEmployeeRequest, Employee>();
    }
}

Separate Profiles When Responsibilities Differ

A large application may have profiles such as EmployeeProfile, DepartmentProfile, and PayrollProfile. You can also keep API contract mappings separate from internal application mappings when that makes ownership clearer.

Scan an Assembly

builder.Services.AddAutoMapper(
    typeof(EmployeeProfile).Assembly);

Assembly scanning discovers profiles in the specified assembly. For multi-project solutions, explicitly select the assemblies that contain mapping profiles rather than relying on accidental discovery.

Registering AutoMapper with Dependency Injection

ASP.NET Core uses dependency injection to create and provide application services. AutoMapper integrates with the Microsoft dependency-injection abstractions and can register an IMapper for injection.

Registration with Configuration

builder.Services.AddAutoMapper(
    cfg =>
    {
        // Optional global configuration.
        // Configure a license key here when your license requires it.
    },
    typeof(EmployeeProfile).Assembly);

AutoMapper 15 introduced licensing and changed registration overloads. AutoMapper 17 retains the modern registration approach. Configure the license key through the approved secret/configuration mechanism when your use requires one; do not hard-code a real key in source control.

Inject IMapper into a Service

public sealed class EmployeeService(IMapper mapper)
{
    public EmployeeResponse GetResponse(Employee employee)
    {
        return mapper.Map<EmployeeResponse>(employee);
    }
}

Prefer injecting IMapper into the component that needs to perform mapping. Avoid creating a new mapper configuration every time a request is handled.

Modern package note: In AutoMapper 13 and later, AddAutoMapper is part of the core AutoMapper package. The separate Microsoft dependency-injection extension package is discontinued for this purpose. Avoid copying old setup instructions that install it unnecessarily.

Custom Member Mapping with ForMember

Use ForMember when a destination property needs a different source member or a custom expression.

Map Different Property Names

public sealed class EmployeeResponse
{
    public int EmployeeId { get; set; }
    public string GivenName { get; set; } = "";
    public string ContactEmail { get; set; } = "";
}

public sealed class EmployeeProfile : Profile
{
    public EmployeeProfile()
    {
        CreateMap<Employee, EmployeeResponse>()
            .ForMember(
                destination => destination.GivenName,
                options => options.MapFrom(source => source.FirstName))
            .ForMember(
                destination => destination.ContactEmail,
                options => options.MapFrom(source => source.Email));
    }
}

Build a Display Name

CreateMap<Employee, EmployeeResponse>()
    .ForMember(
        destination => destination.DisplayName,
        options => options.MapFrom(
            source => source.FirstName + " " + source.LastName));

Expressions used in mapping configuration should be understandable and deterministic. If a transformation contains complex business decisions, move that logic into a domain or application service and map the resulting value.

Ignoring Members and Controlling Destination Values

Not every destination member should be populated from the source. You can explicitly ignore members that should not be mapped by a particular type map.

Ignore a Destination Property

CreateMap<CreateEmployeeRequest, Employee>()
    .ForMember(
        destination => destination.EmployeeId,
        options => options.Ignore())
    .ForMember(
        destination => destination.CreatedAt,
        options => options.Ignore());

This is useful for database-generated identifiers and audit fields that should be assigned by the application or database rather than accepted from a client request.

Why Ignore Is Not Authorization

Ignoring a property in one map does not secure every other path that can update that property. Authorization and update rules must be enforced by the application. Use narrow request DTOs that contain only fields a client is permitted to submit.

Map Only the Intended Update Fields

For updates, consider a dedicated update request model and explicitly configure which fields can change. Do not blindly map an untrusted request onto a tracked entity if that could overwrite protected values.

Flattening and Nested Object Mapping

Real applications often have nested objects. An employee may belong to a department, and a department may contain its own name and identifier. API responses often need a flattened shape rather than the entire object graph.

Nested Source Models

public sealed class Department
{
    public int DepartmentId { get; set; }
    public string DepartmentName { get; set; } = "";
}

public sealed class Employee
{
    public int EmployeeId { get; set; }
    public string FirstName { get; set; } = "";
    public Department? Department { get; set; }
}

Flattened Destination

public sealed class EmployeeResponse
{
    public int EmployeeId { get; set; }
    public string FirstName { get; set; } = "";
    public string? DepartmentDepartmentName { get; set; }
}

AutoMapper can recognize certain flattened naming patterns. For more readable API contracts, explicitly map a property such as DepartmentName from source.Department.DepartmentName.

CreateMap<Employee, EmployeeResponse>()
    .ForMember(
        destination => destination.DepartmentName,
        options => options.MapFrom(
            source => source.Department == null
                ? null
                : source.Department.DepartmentName));

Nested Object to Nested DTO

You can also define a separate map for Department to DepartmentResponse and use it when mapping the employee's nested department. AutoMapper uses the configured type map for the nested destination type.

Source object graph EmployeeFirstNameDepartment└ DepartmentName AutoMapperMapping rules Destination DTO EmployeeResponseFirstNameDepartmentName
A nested source object can be transformed into a simple response contract.
Null awareness: Decide what the destination should contain when an optional nested object is null. Use nullable destination properties and explicit mapping rules where needed.

Mapping Collections

Once a map exists between two element types, AutoMapper can map collections of those types. You generally configure the element mapping rather than creating a separate map for every collection type.

Map a List

CreateMap<Employee, EmployeeResponse>();

List<Employee> employees = GetEmployees();

List<EmployeeResponse> responses =
    mapper.Map<List<EmployeeResponse>>(employees);

Other Collection Shapes

AutoMapper supports common collection interfaces and destination collection types. Ensure the destination type can be created or populated as required by the mapping configuration.

Collection Mapping Does Not Mean Database Querying

Mapping an already-loaded collection is different from asking a database to return only the columns needed for a DTO. For query-side projection, learn ProjectTo and understand the LINQ provider's translation capabilities.

Reverse Mapping and Unflattening

Sometimes you need a mapping in both directions—for example, from an entity to a response DTO and from a request DTO to an entity. You can configure each direction separately or use ReverseMap when the reverse transformation is appropriate.

ReverseMap Example

CreateMap<Employee, EmployeeResponse>()
    .ReverseMap();

Use ReverseMap only when the reverse mapping has the intended semantics. An API response DTO may contain calculated or read-only values that should never be copied back into an entity.

Unflattening

AutoMapper can reverse certain flattening conventions. For example, a flattened destination property such as DepartmentName can map back to a nested source member when the mapping is configured and supported by the reverse map.

Security and design: Do not use a broad reverse map as a shortcut for updating entities from client input. Prefer explicit request-to-entity rules, ignore protected members, and enforce authorization and business invariants in application code.

Null Handling, Conditions and Null Substitution

Null values are common in business applications. A department may be optional, a middle name may be missing, or a source value may not be suitable for a destination property.

Null Substitution

NullSubstitute supplies an alternative value when the source member chain resolves to null.

CreateMap<Employee, EmployeeResponse>()
    .ForMember(
        destination => destination.DepartmentName,
        options => options.NullSubstitute("Unassigned"));

Condition

A condition determines whether a destination member should be mapped after the source value has been resolved.

CreateMap<Employee, EmployeeResponse>()
    .ForMember(
        destination => destination.Salary,
        options => options.Condition(
            source => source.Salary > 0));

PreCondition

A precondition is evaluated before the source value is resolved. It is useful when you want to avoid performing a potentially unnecessary member-resolution step.

Choose Null Semantics Deliberately

  • Use nullable destination members when absence is meaningful.
  • Use a substitute only when a default value is genuinely correct.
  • Do not replace null with an empty string merely to hide missing data.
  • For partial updates, distinguish “not supplied” from “explicitly set to null”.

Value Converters, Resolvers and Type Converters

AutoMapper offers several extension points for custom mapping behavior. Choose the smallest tool that expresses the requirement clearly.

Extension pointScopeTypical use
Value converterOne source member to one destination member.Convert a value into a display format.
Value resolverResolve one destination member using source and context.Calculate a destination field from multiple source values.
Type converterConvert an entire source type to a destination type.Central conversion between two custom types.
Value transformerApply a transformation to values of a configured type.Consistent formatting or normalization.

Value Resolver Example

public sealed class EmployeeDisplayNameResolver
    : IValueResolver<Employee, EmployeeResponse, string>
{
    public string Resolve(
        Employee source,
        EmployeeResponse destination,
        string destinationMember,
        ResolutionContext context)
    {
        return $"{source.FirstName} {source.LastName}".Trim();
    }
}
CreateMap<Employee, EmployeeResponse>()
    .ForMember(
        destination => destination.DisplayName,
        options => options.MapFrom<EmployeeDisplayNameResolver>());

Resolvers are useful for reusable mapping-specific transformations. If the operation needs database access, authorization, external services, or complex business decisions, it usually belongs in an application service rather than a mapping resolver.

Before and After Map Actions

AutoMapper supports before-map and after-map actions. They can run logic immediately before or after a mapping operation.

CreateMap<Employee, EmployeeResponse>()
    .AfterMap((source, destination) =>
    {
        destination.DisplayName =
            $"{source.FirstName} {source.LastName}".Trim();
    });

For simple member transformations, prefer MapFrom or a resolver because the rule remains attached to the destination member. Before/after actions should be used sparingly; they can make mapping side effects harder to discover and test.

For dependency-injected actions, AutoMapper provides IMappingAction<TSource, TDestination>. Keep actions deterministic and avoid using mapping hooks as a substitute for business workflows.

Inheritance and Polymorphic Mapping

Object-oriented applications may use base classes and derived classes. For example, an organization may represent different employee categories using a shared base type and specialized derived types.

AutoMapper supports inheritance mapping configuration through features such as Include and IncludeBase. Configure the inheritance relationship explicitly and test the runtime source types and expected destination types.

CreateMap<Person, PersonResponse>()
    .Include<Employee, EmployeeResponse>();

CreateMap<Employee, EmployeeResponse>()
    .IncludeBase<Person, PersonResponse>();

Polymorphic collection mapping can be useful when a collection contains several derived types. Avoid adding inheritance mapping solely to reduce a few lines of code; the model hierarchy should make sense independently of AutoMapper.

Mapping Enums and Special Types

Enums, identifiers, dates, and other special types deserve deliberate conversion rules. An enum's underlying numeric value is not necessarily a stable API contract.

Enum Mapping

When source and destination enum names align, AutoMapper can map many enum values by convention. If the API uses different names or a different contract, configure and test the conversion explicitly.

Date and Time Values

Be clear about time zones and whether a value represents UTC, local time, a date-only value, or a timestamp. Mapping a DateTime property does not by itself establish correct time-zone semantics.

Value Objects and Identifiers

For custom identifiers or domain value objects, consider whether a simple converter is appropriate. Preserve validation and invariants in the value object rather than relying on mapping to make invalid data valid.

Specialized Extensions

AutoMapper has optional extension packages for particular scenarios, such as enum mapping and expression mapping. Evaluate compatibility, maintenance, and licensing separately before adding an extension. Do not install an extension for a feature that a small explicit mapping already handles clearly.

Projection with ProjectTo and IQueryable

One of AutoMapper's important capabilities is query projection through ProjectTo. Instead of loading full entities and then mapping them in memory, a compatible LINQ provider can translate a projection into a database query that selects the required fields.

In-Memory Mapping vs Query Projection

In-memory MapProjectTo
Maps objects that have already been loaded.Builds a projection expression for an IQueryable.
Uses IMapper.Map.Uses ProjectTo<T>.
Can use runtime mapping features and resolvers.Must use expressions the LINQ provider can translate.
Useful for already-materialized objects.Useful for database-side selection of DTO fields.

Projection Configuration

CreateProjection<Employee, EmployeeResponse>()
    .ForMember(
        destination => destination.DepartmentName,
        options => options.MapFrom(
            source => source.Department!.DepartmentName));

Use ProjectTo with Entity Framework Core

var employees = await dbContext.Employees
    .Where(employee => employee.IsActive)
    .OrderBy(employee => employee.LastName)
    .ProjectTo<EmployeeResponse>(
        mapper.ConfigurationProvider)
    .ToListAsync(cancellationToken);

Apply entity filtering and sorting before projection where possible. ProjectTo must be the last mapping-related step in the LINQ chain because the ORM provider works with entity expressions before the projection to the DTO.

Provider limitation: Not every .NET method or AutoMapper mapping feature can be translated into SQL. Test the generated query against your actual LINQ provider. Runtime resolvers that depend on services are generally not suitable for query projection.

AutoMapper in ASP.NET Core Web API

A common Web API flow receives a request DTO, validates it, passes data to an application service, persists the appropriate model, and returns a response DTO. AutoMapper can handle selected transformations in that flow.

Request DTOHTTP input ServiceBusiness rules AutoMapperObject mapping Entity / DataPersistence layer Response DTOHTTP output Validation and business rules remain application responsibilities
Mapping supports the request/response boundary; it does not replace validation or business logic.

Request DTO

public sealed record CreateEmployeeRequest(
    string FirstName,
    string LastName,
    string Email,
    decimal Salary);

Entity and Response

public sealed class Employee
{
    public int EmployeeId { get; set; }
    public string FirstName { get; set; } = "";
    public string LastName { get; set; } = "";
    public string Email { get; set; } = "";
    public decimal Salary { get; set; }
    public DateTime CreatedAt { get; set; }
}

public sealed class EmployeeResponse
{
    public int EmployeeId { get; set; }
    public string DisplayName { get; set; } = "";
    public string Email { get; set; } = "";
}

Profile

public sealed class EmployeeProfile : Profile
{
    public EmployeeProfile()
    {
        CreateMap<CreateEmployeeRequest, Employee>()
            .ForMember(
                destination => destination.EmployeeId,
                options => options.Ignore())
            .ForMember(
                destination => destination.CreatedAt,
                options => options.Ignore());

        CreateMap<Employee, EmployeeResponse>()
            .ForMember(
                destination => destination.DisplayName,
                options => options.MapFrom(
                    source => $"{source.FirstName} {source.LastName}"));
    }
}

Controller Example

[ApiController]
[Route("api/[controller]")]
public sealed class EmployeesController(
    IEmployeeService employeeService) : ControllerBase
{
    [HttpGet("{id:int}")]
    public async Task<ActionResult<EmployeeResponse>> GetById(
        int id,
        CancellationToken cancellationToken)
    {
        var response = await employeeService.GetByIdAsync(
            id, cancellationToken);

        return response is null ? NotFound() : Ok(response);
    }
}

Keep controllers focused on HTTP concerns. The service should coordinate business operations and mapping where appropriate. Do not move authorization, validation, or complex domain decisions into profiles.

Mapping with EF Core and Dapper

AutoMapper is independent of a specific data-access library. It can map objects retrieved by Entity Framework Core, Dapper, ADO.NET, or another source into application DTOs.

With Entity Framework Core

Use ProjectTo when the provider can translate the mapping expression and you want the database to select only the required columns. Use Map when entities have already been materialized or when runtime mapping behavior is required.

With Dapper

Dapper maps SQL result rows to the type you request. You can then use AutoMapper to map that result object to a separate API DTO. For simple SQL projections, it may be clearer to query directly into the DTO and avoid an unnecessary second mapping step.

// Dapper can query directly into a response DTO
var responses = await connection.QueryAsync<EmployeeResponse>(
    """
    SELECT
        EmployeeId,
        FirstName + ' ' + LastName AS DisplayName,
        Email
    FROM Employees;
    """);

Choose the mapping location that makes the data flow easiest to understand. Do not introduce multiple mapping layers when one SQL projection or one explicit transformation is enough.

Configuration Validation and Testing

A mapping configuration can compile while still containing mistakes, such as an unmapped destination property or an incorrect source member. Validate configuration early and add tests for important transformations.

Assert Configuration Is Valid

var configuration = new MapperConfiguration(
    cfg => cfg.AddProfile<EmployeeProfile>(),
    loggerFactory);

configuration.AssertConfigurationIsValid();

In an ASP.NET Core application, you can validate the registered configuration in a test by resolving the IConfigurationProvider from the service provider and calling AssertConfigurationIsValid().

Example Mapping Test

[Fact]
public void Employee_maps_to_response()
{
    var configuration = new MapperConfiguration(
        cfg => cfg.AddProfile<EmployeeProfile>(),
        NullLoggerFactory.Instance);

    var mapper = configuration.CreateMapper();

    var employee = new Employee
    {
        EmployeeId = 7,
        FirstName = "Ava",
        LastName = "Sharma",
        Email = "ava@example.com"
    };

    var response = mapper.Map<EmployeeResponse>(employee);

    Assert.Equal(7, response.EmployeeId);
    Assert.Equal("Ava Sharma", response.DisplayName);
    Assert.Equal("ava@example.com", response.Email);
}

This test focuses on the mapping result. In a real project, also test null cases, ignored fields, nested objects, collection behavior, and any custom resolver or converter.

What to Test

  • Every important source/destination map is registered.
  • Required destination members receive the expected values.
  • Protected and server-generated fields are not overwritten.
  • Null and optional nested values behave correctly.
  • Collection and inheritance mappings produce the expected types.
  • Projection expressions translate correctly against the actual provider.

Performance and Production Best Practices

AutoMapper is designed to make repetitive object mapping easier, but it is still important to understand when mapping occurs and how much data is being processed.

Recommended Practices

  • Configure maps once during application startup.
  • Use profiles organized around features or clear model boundaries.
  • Validate configuration during development and automated tests.
  • Prefer ProjectTo for supported query-side projections where it reduces unnecessary entity materialization.
  • Do not use mapping profiles for database calls or external service calls.
  • Keep mapping expressions simple and predictable.
  • Use dedicated request DTOs to limit client-controlled fields.
  • Measure before optimizing; avoid assumptions about mapping overhead.
  • Review mappings when DTOs or entities change.

Mapping Is Not a Substitute for Efficient Queries

Mapping 100,000 fully materialized entities to 100,000 DTOs still requires loading and processing those entities. If you only need five columns, query projection may be a better design.

Be Careful with Large Object Graphs

Mapping deep or cyclic object graphs can create unnecessary work and unexpected output shapes. Prefer purpose-built DTOs that represent exactly what the application needs to return.

Logging and Diagnostics

When diagnosing mapping issues, inspect the configured type map and test representative input values. Do not log sensitive entity or DTO contents merely to debug a mapping failure.

Common Mistakes and Troubleshooting

1. Missing Type Map Configuration

AutoMapper does not create arbitrary maps automatically. Define the required source and destination map in a profile and make sure the profile assembly is registered.

2. Profile Assembly Is Not Registered

Check the assembly passed to AddAutoMapper. If a profile lives in another project, include that assembly explicitly.

3. Property Names Do Not Match

Use ForMember and MapFrom to define the source for a differently named destination member.

4. Null Nested Objects

Use nullable destination members and explicit null-handling rules. Do not assume every navigation property is loaded or populated.

5. Mapping an API Request Directly onto a Database Entity

This can overwrite identifiers, audit values, ownership fields, or other protected data. Use narrow request models and explicit ignore rules, then enforce business and authorization checks.

6. Using Runtime Resolvers in ProjectTo

Query providers translate expression trees; they cannot generally translate arbitrary runtime services or resolver code. Keep projection expressions provider-compatible.

7. Using ReverseMap Everywhere

Reverse mapping is convenient only when both directions make sense. Configure request-to-entity maps explicitly when update semantics differ from response semantics.

8. Hiding Business Logic in Mapping Profiles

Profiles should describe transformations, not make business decisions that require repositories, authorization, or workflow orchestration.

9. Following Outdated Setup Tutorials

Older examples may install a separate dependency-injection extension package or use registration overloads that changed in later releases. Follow documentation for the version installed in your project.

When to Use AutoMapper—and When Not To

AutoMapper Is a Good Fit When

  • You have many repetitive entity-to-DTO or DTO-to-model transformations.
  • Most mappings follow clear naming and type conventions.
  • Mapping rules can be expressed simply and tested centrally.
  • You need consistent flattening or collection mapping.
  • Your team understands and maintains the mapping configuration.

Manual Mapping May Be Better When

  • The mapping is very small and occurs in one place.
  • The transformation contains important business decisions.
  • The destination requires carefully controlled construction.
  • Explicit assignments are easier to review than indirect configuration.
  • The mapping is performance-critical and profiling shows the abstraction is unnecessary.

Do Not Use AutoMapper to

  • Replace request validation.
  • Implement authorization or field-level access control.
  • Hide database access inside resolvers.
  • Automatically synchronize arbitrary object graphs.
  • Turn every pair of classes into a map without a clear requirement.
Professional guideline: Use AutoMapper to remove repetitive transformation code, not to hide application behavior. A good mapping should be easy to locate, understand, validate, and test.

Licensing and Version Considerations

AutoMapper's licensing model changed beginning with version 15. AutoMapper is dual licensed, and current releases include license configuration and license validation behavior. Review the current license terms for your project, organization, and deployment scenario before adopting it.

Configure a License Key

For applications with a valid license key, configure it through dependency injection or an environment variable. Avoid committing a real key to source control.

builder.Services.AddAutoMapper(
    cfg =>
    {
        cfg.LicenseKey =
            builder.Configuration["AutoMapper:LicenseKey"];
    },
    typeof(EmployeeProfile).Assembly);

Alternatively, the current documentation describes environment-variable discovery through AUTOMAPPER_LICENSE_KEY and LUCKYPENNY_LICENSE_KEY. Use a secure secret-management mechanism in deployed environments.

Version Facts for This Guide

ComponentVersion / detail
.NET.NET 10 (LTS)
C#C# 14 (default language version for .NET 10)
AutoMapper17.0.0 stable, verified November 15, 2026
Target frameworknet10.0
DI registrationAddAutoMapper is in the core package
LicenseReview applicable AutoMapper license terms and configure a valid key where required
Important: AutoMapper 17 is a major release that targets .NET 10 and .NET 11. For a .NET 10 project, use the package version shown above and consult the 17.0 upgrade guide when upgrading from earlier major versions.

Practical Projects and Learning Roadmap

Build a small application and add mapping features progressively. This makes each concept easier to understand and gives you examples you can discuss in technical interviews.

Project 1: Employee API

  • Create entity, request, and response models.
  • Configure basic mappings with a profile.
  • Register AutoMapper with dependency injection.
  • Return response DTOs from API endpoints.

Project 2: Employee and Department Models

  • Create nested entity relationships.
  • Flatten department information into an employee response.
  • Handle null department values.
  • Map employee collections.

Project 3: Create and Update Workflows

  • Map create requests to entities.
  • Ignore generated IDs and audit fields.
  • Use explicit update rules for protected properties.
  • Test null and partial-update behavior.

Project 4: EF Core Projection

  • Use ProjectTo for a read-only listing endpoint.
  • Inspect generated SQL.
  • Compare projection with loading entities and calling Map.
  • Test provider translation.

Project 5: Mapping Quality and Tests

  • Add configuration validation.
  • Write unit tests for important profiles.
  • Add tests for nested and collection mappings.
  • Review licensing and package updates.

Suggested Learning Sequence

  1. Understand why applications use entities, DTOs, and request models.
  2. Learn manual mapping and the purpose of AutoMapper.
  3. Install AutoMapper and create your first profile.
  4. Understand conventions and custom member rules.
  5. Register profiles with ASP.NET Core dependency injection.
  6. Practice nested objects, flattening, collections, and reverse maps.
  7. Learn null handling, converters, resolvers, and conditions.
  8. Use ProjectTo with a supported LINQ provider.
  9. Validate configuration and write mapping tests.
  10. Apply security, licensing, and production design practices.

Important AutoMapper Interview Questions

1. What is AutoMapper?

AutoMapper is a convention-based object-to-object mapping library for .NET. It transfers data between configured source and destination types.

2. What problem does AutoMapper solve?

It reduces repetitive property-assignment code when converting entities, DTOs, request models, and other application types.

3. What is a Profile?

A profile is a class used to group related mapping configurations.

4. What is CreateMap?

CreateMap<TSource, TDestination>() defines a mapping between a source type and a destination type.

5. What is IMapper?

IMapper is the service used to execute configured mappings, commonly through methods such as Map.

6. What is ForMember?

ForMember configures how an individual destination member should be populated, ignored, or otherwise handled.

7. What is MapFrom?

MapFrom identifies the source member or expression used to populate a destination member.

8. What is ReverseMap?

ReverseMap creates a reverse mapping configuration. Use it only when the reverse transformation has the correct semantics.

9. What is a value resolver?

A value resolver provides custom logic for resolving a destination member.

10. What is a type converter?

A type converter defines a conversion between an entire source type and destination type.

11. What is ProjectTo?

ProjectTo builds a projection expression for an IQueryable so a compatible provider can translate it, often into a database query.

12. What is the difference between Map and ProjectTo?

Map transforms objects in memory. ProjectTo projects an IQueryable and depends on provider translation.

13. How do you validate mapping configuration?

Use AssertConfigurationIsValid() in development and automated tests to identify configuration problems.

14. Does AutoMapper replace model validation?

No. Request validation and business validation remain application responsibilities.

15. Can AutoMapper map collections?

Yes. Once the element type map is configured, AutoMapper can map supported collection types.

16. Can AutoMapper work with Dapper?

Yes. Dapper can retrieve objects and AutoMapper can transform those objects into other application types. Avoid an extra mapping step when SQL can return the desired DTO directly.

17. Does AutoMapper work with Entity Framework Core?

Yes. It can map materialized entities in memory or use ProjectTo for supported query projections.

18. Does AutoMapper require a license?

AutoMapper introduced a licensing requirement in version 15. Review the current dual-license terms and configure a valid key where required.

19. Is AutoMapper always better than manual mapping?

No. Manual mapping may be clearer for small or business-sensitive transformations. Choose the approach that is easiest to maintain and test.

20. What changed in modern AutoMapper setup?

AddAutoMapper is included in the core package in modern versions. AutoMapper 15 introduced licensing and registration changes; AutoMapper 17 targets .NET 10 and .NET 11.

Official Learning Resources

Use the official documentation and package sources to verify APIs, supported frameworks, upgrade instructions, and licensing details.

Final Thought

Object mapping is a small but important part of application architecture. It helps keep database entities, domain models, API requests, and response contracts independent while allowing information to move between them in a controlled way.

AutoMapper is valuable when its conventions and configuration reduce repetitive code without hiding important behavior. Learn the fundamentals first, keep profiles focused, validate your configuration, test the mappings that matter, and use manual code whenever it communicates the transformation more clearly.

Good mapping design is not about mapping everything automatically. It is about making data boundaries intentional, secure, and easy to maintain.

Skills Tutorials

Learn • Build • Grow Together