AutoMapper для C# (.NET): полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в AutoMapper для C# (.NET)

В современной разработке на C# (.NET) часто возникает задача преобразования одного объекта в другой. Это может быть необходимо при передаче данных между слоями приложения (например, из базы данных в DTO для API), при маппинге ViewModel в Entity, или при работе с различными версиями API. Ручное написание кода для каждого такого преобразования — это утомительная, подверженная ошибкам и трудно поддерживаемая работа. Именно здесь на помощь приходит AutoMapper.

AutoMapper — это популярная библиотека с открытым исходным кодом для .NET, которая автоматизирует процесс маппинга (сопоставления) свойств одного объекта с другим. Она основана на принципе «соглашение по конфигурации» (convention over configuration). Это означает, что если имена и типы свойств в исходном и целевом объектах совпадают (или следуют определенным правилам), AutoMapper может выполнить маппинг без явного указания правил. Если же правила отличаются, вы можете легко настроить их с помощью гибкого API.

Библиотека значительно сокращает объем шаблонного кода, делает код чище, уменьшает количество ошибок, связанных с ручным присваиванием, и упрощает рефакторинг. AutoMapper широко используется в проектах на ASP.NET Core, WPF, Xamarin, и других .NET-приложениях.

Установка AutoMapper

AutoMapper распространяется через NuGet — официальный менеджер пакетов для .NET. Установить его можно несколькими способами:

1. Через консоль диспетчера пакетов (Package Manager Console)

Install-Package AutoMapper

2. Через .NET CLI

dotnet add package AutoMapper

3. Через интерфейс Visual Studio (Manage NuGet Packages)

Откройте проект -> ПКМ по проекту -> "Manage NuGet Packages" -> найдите "AutoMapper" -> установите последнюю стабильную версию.

Для работы с ASP.NET Core также рекомендуется установить пакет AutoMapper.Extensions.Microsoft.DependencyInjection, который упрощает интеграцию с DI-контейнером:

dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection

Быстрый старт: первый маппинг

Давайте создадим простейший пример. У нас есть класс UserEntity (из базы данных) и UserDto (для передачи на клиент).

using AutoMapper;

// 1. Исходный и целевой классы
public class UserEntity
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
}

public class UserDto
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
}

// 2. Создание конфигурации и Mapper
var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<UserEntity, UserDto>();
});

var mapper = config.CreateMapper();

// 3. Использование
var userEntity = new UserEntity
{
    Id = 1,
    FirstName = "Иван",
    LastName = "Петров",
    Email = "ivan@example.com"
};

var userDto = mapper.Map<UserDto>(userEntity);

Console.WriteLine(userDto.FirstName); // Выведет: Иван

В этом примере мы создали карту (Map) между UserEntity и UserDto. Поскольку имена свойств совпадают, AutoMapper автоматически сопоставил их. Метод Map<TDestination>(source) выполняет маппинг и возвращает новый объект целевого типа.

Основные методы и функции AutoMapper

AutoMapper предоставляет богатый API для настройки маппинга. Ниже приведены 10 ключевых методов и функций, которые вы будете использовать чаще всего.

1. CreateMap<TSource, TDestination>()

Что делает: Создает конфигурацию маппинга между двумя типами. Это основной метод для объявления карты.

Параметры: Два generic-параметра: TSource (исходный тип) и TDestination (целевой тип).

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Order, OrderDto>();
});

2. ForMember()

Что делает: Позволяет настроить маппинг для конкретного свойства целевого объекта. Используется, когда имя свойства не совпадает или требуется преобразование.

Параметры: ForMember(dest => dest.PropertyName, options => ...). В options можно использовать MapFrom, Ignore, UseValue и др.

cfg.CreateMap<UserEntity, UserDto>()
    .ForMember(dest => dest.FullName, opt => opt.MapFrom(src => $"{src.FirstName} {src.LastName}"))
    .ForMember(dest => dest.Password, opt => opt.Ignore()); // Игнорируем пароль

3. MapFrom()

Что делает: Указывает, из какого свойства или выражения исходного объекта брать значение для целевого свойства. Используется внутри ForMember.

Параметры: Лямбда-выражение, которое получает исходный объект и возвращает значение.

.ForMember(dest => dest.CreatedDateFormatted, opt => opt.MapFrom(src => src.CreatedDate.ToString("yyyy-MM-dd")))

4. Ignore()

Что делает: Указывает, что свойство целевого объекта должно быть пропущено при маппинге (останется со значением по умолчанию).

Параметры: Не принимает дополнительных параметров, вызывается на объекте IMemberConfigurationExpression.

.ForMember(dest => dest.InternalId, opt => opt.Ignore())

5. ReverseMap()

Что делает: Создает обратную карту (из TDestination в TSource) на основе уже определенной прямой карты. Экономит время, если маппинг симметричный.

Параметры: Не принимает.

cfg.CreateMap<UserEntity, UserDto>()
    .ForMember(dest => dest.FullName, opt => opt.Ignore())
    .ReverseMap(); // Теперь можно маппить и UserDto в UserEntity

6. ConstructUsing()

Что делает: Позволяет указать пользовательскую логику создания целевого объекта. Это полезно, если конструктор целевого класса требует параметры.

Параметры: Функция, которая принимает исходный объект и возвращает новый экземпляр целевого типа.

cfg.CreateMap<Order, OrderDto>()
    .ConstructUsing(src => new OrderDto(src.Id, src.TotalPrice * 1.2m)); // Пример с наценкой

7. AfterMap() / BeforeMap()

Что делает: Позволяют выполнить произвольный код до или после маппинга. Полезно для валидации, логирования или пост-обработки.

Параметры: Action<TSource, TDestination>.

cfg.CreateMap<UserEntity, UserDto>()
    .BeforeMap((src, dest) => Console.WriteLine($"Маппинг пользователя {src.Id}"))
    .AfterMap((src, dest) => dest.CreatedAt = DateTime.UtcNow);

8. ForAllOtherMembers()

Что делает: Позволяет применить общее правило ко всем свойствам, которые не были явно настроены. Используется редко, но может быть полезно для глобальных настроек.

Параметры: Action<IMemberConfigurationExpression>.

cfg.CreateMap<Source, Dest>()
    .ForAllOtherMembers(opt => opt.Condition((src, dest, srcMember) => srcMember != null)); // Не маппить null

9. ValueTransformers

Что делает: Позволяет применить глобальное преобразование ко всем значениям определенного типа. Например, обрезать строки или преобразовывать даты.

Параметры: AddTransform<TType>(Func<TType, TType>).

var config = new MapperConfiguration(cfg =>
{
    cfg.ValueTransformers.Add<string>(val => val?.Trim()); // Обрезать все строки
    cfg.CreateMap<UserEntity, UserDto>();
});

10. ProjectTo<TDestination>()

Что делает: Ключевой метод для работы с Entity Framework. Он не выполняет маппинг в памяти, а транслирует его в SQL-запрос. Это позволяет загружать из базы данных только те поля, которые нужны в DTO, что повышает производительность.

Параметры: Generic-параметр TDestination. Вызывается на IQueryable.

using (var context = new AppDbContext())
{
    var usersDto = context.Users
        .ProjectTo<UserDto>(mapper.ConfigurationProvider)
        .ToList();
    // SQL запрос будет содержать только поля из UserDto
}

Реальный практический пример: API для интернет-магазина

Рассмотрим более сложный пример. Представим, что мы разрабатываем API для интернет-магазина. У нас есть сущность Product и DTO ProductResponse для ответа клиенту. Также нам нужно маппить запрос на создание продукта CreateProductRequest в сущность Product.

using AutoMapper;

// --- Модели ---
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
    public DateTime CreatedAt { get; set; }
    public int CategoryId { get; set; }
    public Category Category { get; set; }
    public bool IsDeleted { get; set; } // Софт-удаление
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class ProductResponse
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
    public string CategoryName { get; set; } // Имя категории, а не ID
    public string CreatedAtFormatted { get; set; } // Отформатированная дата
}

public class CreateProductRequest
{
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
    public int CategoryId { get; set; }
}

// --- Профиль маппинга (рекомендуемый способ организации) ---
public class ProductProfile : Profile
{
    public ProductProfile()
    {
        // Маппинг Product -> ProductResponse
        CreateMap<Product, ProductResponse>()
            .ForMember(dest => dest.CategoryName, opt => opt.MapFrom(src => src.Category.Name))
            .ForMember(dest => dest.CreatedAtFormatted, opt => opt.MapFrom(src => src.CreatedAt.ToString("dd.MM.yyyy HH:mm")))
            .ForMember(dest => dest.Description, opt => opt.Condition(src => !string.IsNullOrEmpty(src.Description))); // Не маппить пустое описание

        // Маппинг CreateProductRequest -> Product
        CreateMap<CreateProductRequest, Product>()
            .ForMember(dest => dest.Id, opt => opt.Ignore()) // ID генерируется БД
            .ForMember(dest => dest.CreatedAt, opt => opt.MapFrom(src => DateTime.UtcNow))
            .ForMember(dest => dest.IsDeleted, opt => opt.MapFrom(src => false))
            .ForMember(dest => dest.Category, opt => opt.Ignore()); // Катего                    

Рекомендаций