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

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

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

Начать курс

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

Mapster — это высокопроизводительная библиотека для маппинга объектов (object-to-object mapping) в экосистеме .NET. Она позволяет автоматически преобразовывать объекты одного типа в другой, избавляя разработчика от написания рутинного кода по копированию свойств. В отличие от таких монстров, как AutoMapper, Mapster делает упор на скорость работы и минимальное потребление памяти, что подтверждается многочисленными бенчмарками.

Зачем нужен маппинг объектов? В реальных проектах мы постоянно сталкиваемся с необходимостью преобразования данных: DTO (Data Transfer Object) в Entity, ViewModel в Model, Model в Response и так далее. Ручное копирование каждого поля — это скучно, чревато ошибками и усложняет поддержку кода. Mapster решает эту проблему, предоставляя простой, гибкий и быстрый API для автоматического маппинга.

Mapster генерирует код маппинга на этапе компиляции (через Roslyn-анализаторы) или использует динамическую компиляцию выражений. Это делает его одним из самых быстрых мапперов в .NET, часто обгоняя даже ручное копирование в некоторых сценариях. Библиотека поддерживает сложные сценарии: вложенные объекты, коллекции, условный маппинг, игнорирование полей, адаптеры для разных типов и многое другое.

Установка

Установка Mapster производится через NuGet Package Manager. Выполните одну из следующих команд в консоли диспетчера пакетов:

dotnet add package Mapster

Или через Package Manager Console:

Install-Package Mapster

Для работы с ASP.NET Core и внедрением зависимостей (DI) рекомендуется установить дополнительный пакет:

dotnet add package Mapster.DependencyInjection

После установки добавьте using Mapster; в начало вашего файла.

Быстрый старт

Рассмотрим простейший пример: у нас есть класс User (модель из базы данных) и класс UserDto (для передачи на клиент).

using Mapster;

// Исходный класс
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime CreatedAt { get; set; }
}

// Целевой класс
public class UserDto
{
    public int Id { get; set; }
    public string FullName { get; set; }
    public string Email { get; set; }
}

// Пример использования
public class Program
{
    public static void Main()
    {
        var user = new User
        {
            Id = 1,
            Name = "Иван Петров",
            Email = "ivan@example.com",
            CreatedAt = DateTime.Now
        };

        // Маппинг с помощью Mapster
        var dto = user.Adapt<UserDto>();

        Console.WriteLine($"Id: {dto.Id}, Name: {dto.FullName}, Email: {dto.Email}");
        // Вывод: Id: 1, Name: Иван Петров, Email: ivan@example.com
    }
}

Обратите внимание: свойство Name было автоматически сопоставлено с FullName, так как Mapster по умолчанию пытается найти соответствия по имени (регистронезависимо). Если бы в UserDto было свойство Name, оно бы тоже сопоставилось.

Основные методы и классы

Рассмотрим ключевые методы Mapster. Для каждого приведена сигнатура, описание и пример.

1. Adapt<TDestination>(object source)

Сигнатура: public static TDestination Adapt<TDestination>(this object source)

Описание: Базовый метод расширения для маппинга одного объекта в другой. Создает новый экземпляр TDestination и копирует значения свойств из source.

var user = new User { Id = 1, Name = "Анна" };
var dto = user.Adapt<UserDto>();
Console.WriteLine(dto.FullName); // Анна

2. Adapt(object source, Type sourceType, Type destinationType)

Сигнатура: public static object Adapt(this object source, Type sourceType, Type destinationType)

Описание: Позволяет выполнить маппинг, когда типы известны только во время выполнения (runtime). Возвращает object, который нужно привести к нужному типу.

object user = new User { Id = 2, Name = "Борис" };
Type sourceType = typeof(User);
Type destType = typeof(UserDto);
var result = user.Adapt(sourceType, destType) as UserDto;
Console.WriteLine(result.FullName); // Борис

3. TypeAdapterConfig<TSource, TDestination>.NewConfig()

Сигнатура: public static TypeAdapterConfig<TSource, TDestination> NewConfig()

Описание: Создает новую конфигурацию маппинга для пары типов. Позволяет задавать правила: игнорирование полей, переименование, условный маппинг, форматирование.

TypeAdapterConfig<User, UserDto>.NewConfig()
    .Ignore(dest => dest.Email) // Не копировать Email
    .Map(dest => dest.FullName, src => src.Name.ToUpper()); // Преобразовать имя в верхний регистр

var user = new User { Id = 3, Name = "Виктор", Email = "v@mail.ru" };
var dto = user.Adapt<UserDto>();
Console.WriteLine($"{dto.FullName}, Email: {dto.Email}"); // ВИКТОР, Email: (пусто)

4. TypeAdapterConfig.GlobalSettings.Default.IgnoreNonMapped()

Сигнатура: public TypeAdapterSettings IgnoreNonMapped(bool ignore = true)

Описание: Глобальная настройка, которая заставляет Mapster игнорировать все свойства, для которых нет соответствия в целевом типе. По умолчанию Mapster выбрасывает исключение при несовпадении имен.

TypeAdapterConfig.GlobalSettings.Default.IgnoreNonMapped(true);

var user = new User { Id = 4, Name = "Галина", Email = "g@mail.ru", CreatedAt = DateTime.Now };
var dto = user.Adapt<UserDto>(); // CreatedAt будет проигнорирован, ошибки нет
Console.WriteLine(dto.FullName); // Галина

5. TypeAdapterConfig<TSource, TDestination>.Map(destMember, sourceMember)

Сигнатура: public TypeAdapterConfig<TSource, TDestination> Map(Expression<Func<TDestination, object>> destMember, Expression<Func<TSource, object>> sourceMember)

Описание: Явно указывает, какое свойство источника должно быть сопоставлено с каким свойством цели. Используется для переименования или вычисления значений.

TypeAdapterConfig<User, UserDto>.NewConfig()
    .Map(dest => dest.FullName, src => $"{src.Name} (ID: {src.Id})");

var user = new User { Id = 5, Name = "Дмитрий" };
var dto = user.Adapt<UserDto>();
Console.WriteLine(dto.FullName); // Дмитрий (ID: 5)

6. TypeAdapterConfig<TSource, TDestination>.Ignore(destMember)

Сигнатура: public TypeAdapterConfig<TSource, TDestination> Ignore(Expression<Func<TDestination, object>> destMember)

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

TypeAdapterConfig<User, UserDto>.NewConfig()
    .Ignore(dest => dest.Id); // Id не будет скопирован

var user = new User { Id = 6, Name = "Елена" };
var dto = user.Adapt<UserDto>();
Console.WriteLine(dto.Id); // 0 (значение по умолчанию для int)

7. TypeAdapterConfig<TSource, TDestination>.IgnoreAttribute<TAttribute>()

Сигнатура: public TypeAdapterConfig<TSource, TDestination> IgnoreAttribute<TAttribute>(bool inherit = true) where TAttribute : Attribute

Описание: Позволяет игнорировать все свойства, помеченные определенным атрибутом. Например, если у вас есть кастомный атрибут [SensitiveData], вы можете исключить такие поля из маппинга.

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    [SensitiveData]
    public string Password { get; set; }
}

TypeAdapterConfig.GlobalSettings.Default.IgnoreAttribute<SensitiveData>();

var user = new User { Id = 7, Name = "Жанна", Password = "secret123" };
var dto = user.Adapt<UserDto>(); // Password будет проигнорирован

8. QueryableExtensions.ProjectToType<TDestination>(IQueryable source)

Сигнатура: public static IQueryable<TDestination> ProjectToType<TDestination>(this IQueryable source)

Описание: Расширение для IQueryable, которое позволяет выполнять проекцию прямо в SQL-запросе (через Entity Framework Core или другую ORM). Это самый производительный способ маппинга, так как данные преобразуются на стороне базы данных.

using (var context = new AppDbContext())
{
    var dtos = context.Users
        .Where(u => u.IsActive)
        .ProjectToType<UserDto>()
        .ToList();
    // SQL: SELECT Id, Name AS FullName, Email FROM Users WHERE IsActive = 1
}

9. TypeAdapterConfig<TSource, TDestination>.ConstructUsing(Func<TSource, TDestination> factory)

Сигнатура: public TypeAdapterConfig<TSource, TDestination> ConstructUsing(Func<TSource, TDestination> factory)

Описание: Позволяет задать кастомный способ создания целевого объекта. Например, если у вас есть конструктор с параметрами или нужно использовать фабрику.

TypeAdapterConfig<User, UserDto>.NewConfig()
    .ConstructUsing(src => new UserDto
    {
        Id = src.Id,
        FullName = $"Mr./Ms. {src.Name}"
    });

var user = new User { Id = 8, Name = "Зоя" };
var dto = user.Adapt<UserDto>();
Console.WriteLine(dto.FullName); // Mr./Ms. Зоя

10. TypeAdapterConfig<TSource, TDestination>.AfterMapping(Action<TSource, TDestination> action)

Сигнатура: public TypeAdapterConfig<TSource, TDestination> AfterMapping(Action<TSource, TDestination> action)

Описание: Выполняет дополнительное действие после завершения маппинга. Полезно для пост-обработки: валидации, логирования, вычисления дополнительных полей.

TypeAdapterConfig<User, UserDto>.NewConfig()
    .AfterMapping((src, dest) =>
    {
        dest.FullName = dest.FullName?.Trim();
        Console.WriteLine($"Mapped user {src.Id} to DTO");
    });

var user = new User { Id = 9, Name = "Игорь " };
var dto = user.Adapt<UserDto>();
Console.WriteLine($"'{dto.FullName}'"); // 'Игорь' (пробел удален)

Реальный практический пример

Рассмотрим более сложный сценарий: веб-API для управления заказами. У нас есть сущности базы данных, DTO для запросов и ответов, а также необходимость вложенного маппинга.

using Mapster;
using System;
using System.Collections.Generic;
using System.Linq;

// ===== Модели данных (Entity) =====
public class Customer
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public DateTime RegistrationDate { get; set; }
    public List<Order> Orders { get; set; } = new();
}

public class Order
{
    public int Id { get; set; }
    public DateTime OrderDate { get; set; }
    public decimal TotalAmount { get; set; }
    public int CustomerId { get; set; }
    public Customer Customer { get; set; }
    public List<OrderItem> Items { get; set; } = new();
}

public class OrderItem
{
    public int Id { get; set; }
    public string ProductName { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
}

// ===== DTO =====
public class CustomerResponse
{
    public int Id { get; set; }
    public string FullName { get; set; }
    public string Email { get; set; }
    public int OrderCount { get; set; }
    public List<OrderResponse> Orders { get; set; }
}

public class OrderResponse
{
    public int Id { get; set; }
    public string DateFormatted { get; set; }
    public decimal Total { get; set; }
    public int ItemCount { get; set; }
}

public class CreateOrderRequest
{
    public int CustomerId { get; set; }
    public List<CreateOrderItemRequest> Items { get; set; }
}

public class CreateOrderItemRequest
{
    public string ProductName {                    

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