Getting Started with Pragmatic.Mapping
This guide walks you through creating your first entity-to-DTO mapping with Pragmatic.Mapping.
Prerequisites
Section titled “Prerequisites”- .NET 10.0+
Pragmatic.MappingpackagePragmatic.SourceGeneratorreferenced as an analyzer
Step 1: Define Your Entity
Section titled “Step 1: Define Your Entity”Entities are your domain objects. They can be classes, records, or structs.
public class Guest{ public Guid Id { get; set; } public string FirstName { get; set; } = ""; public string LastName { get; set; } = ""; public string Email { get; set; } = ""; public string? Phone { get; set; } public string? Nationality { get; set; } public string PreferredLanguage { get; set; } = "";}Step 2: Create a DTO with [MapFrom<T>]
Section titled “Step 2: Create a DTO with [MapFrom<T>]”Decorate a partial type with [MapFrom<T>]. The type must be partial (PRAG0300 error otherwise).
using Pragmatic.Mapping.Attributes;
[MapFrom<Guest>][GenerateProjection]public partial class GuestDto{ public Guid Id { get; init; } public string FirstName { get; init; } = ""; public string LastName { get; init; } = ""; public string Email { get; init; } = ""; public string? Phone { get; init; } public string? Nationality { get; init; } public string PreferredLanguage { get; init; } = "";
// Computed property -- excluded from mapping [MapIgnore] public string FullName => $"{FirstName} {LastName}";}Properties are matched by name (case-insensitive). Properties that exist on both the entity and DTO are mapped automatically.
Step 3: Use the Generated Code
Section titled “Step 3: Use the Generated Code”After building, the source generator creates several ways to map:
// Option 1: Static factory methodvar dto = GuestDto.FromEntity(guest);
// Option 2: Extension method on a single entityvar dto = guest.ToGuestDto();
// Option 3: Extension method on a collectionIEnumerable<GuestDto> dtos = guests.ToGuestDto();List<GuestDto> dtoList = guestList.ToGuestDto();GuestDto[] dtoArray = guestArray.ToGuestDto();
// Option 4: In-memory selector for LINQvar dtos = guests.Select(GuestDto.Selector).ToList();
// Option 5: EF Core projection (requires [GenerateProjection])var dtos = await db.Guests .Where(g => g.Email != null) .Select(GuestDto.Projection) .ToListAsync();What Gets Generated
Section titled “What Gets Generated”For a [MapFrom<Guest>] DTO, the generator produces two files:
1. Main partial class (GuestDto.Mapping.g.cs)
Section titled “1. Main partial class (GuestDto.Mapping.g.cs)”public partial class GuestDto{ public static GuestDto FromEntity(Guest entity) { ... } public static Func<Guest, GuestDto> Selector { get; } = FromEntity; public static Expression<Func<Guest, GuestDto>> Projection { get; } = ...;
static partial void BeforeMapping(Guest source, ref GuestDto? result); static partial void CustomizeMapping(Guest source, ref GuestDtoMappingContext ctx);
public struct GuestDtoMappingContext { ... }}2. Extension methods (GuestDto.Extensions.g.cs)
Section titled “2. Extension methods (GuestDto.Extensions.g.cs)”public static class GuestDtoMappingExtensions{ public static GuestDto ToGuestDto(this Guest entity) => GuestDto.FromEntity(entity); public static IEnumerable<GuestDto> ToGuestDto(this IEnumerable<Guest> entities) => ...; public static List<GuestDto> ToGuestDto(this List<Guest> entities) => ...; public static GuestDto[] ToGuestDto(this Guest[] entities) => ...;}Common Patterns
Section titled “Common Patterns”Flattening Navigation Properties
Section titled “Flattening Navigation Properties”When a DTO property name follows the pattern {NavigationName}{PropertyName}, the generator flattens automatically:
// Entitypublic class RoomType{ public Property Property { get; set; } // Navigation}
// DTO -- AddressCity auto-resolved? No, use [MapProperty] for navigation paths[MapFrom<RoomType>]public partial class RoomTypeSummaryDto{ [MapProperty("Property.Name")] public string PropertyName { get; init; } = "";}Excluding Properties
Section titled “Excluding Properties”Use [MapIgnore] for computed properties or properties populated by other means:
[MapFrom<Property>]public partial class PropertyDetailDto{ public string Name { get; init; } = ""; public string City { get; init; } = ""; public int StarRating { get; init; }
[MapIgnore] public string DisplayLabel => $"{Name} ({City}, {StarRating})";
[MapIgnore] public IReadOnlyDictionary<string, string>? Descriptions { get; init; }}Format Strings
Section titled “Format Strings”Use [MapProperty(Format = "...")] to format values via .ToString(format):
[MapFrom<Property>]public partial class PropertyDetailDto{ [MapProperty("CreatedAt", Format = "yyyy-MM-dd")] public string CreatedDate { get; init; } = "";}Note: Format strings are not supported in projections (PRAG0321 warning) because .ToString(format) cannot be translated to SQL.
Bidirectional Mapping
Section titled “Bidirectional Mapping”A single DTO can have both [MapFrom<T>] and [MapTo<T>]:
[MapFrom<User>][MapTo<User>]public partial class UserDto{ public Guid Id { get; init; } public string Name { get; init; } = "";}
// Read pathvar dto = UserDto.FromEntity(user);
// Write pathvar entity = dto.ToEntity();dto.ApplyTo(existingEntity);DTO-to-Entity (Write Path)
Section titled “DTO-to-Entity (Write Path)”Use [MapTo<T>] for create operations:
[MapTo<Guest>]public partial record CreateGuestDto{ public string FirstName { get; init; } = ""; public string LastName { get; init; } = ""; public string Email { get; init; } = "";}
// Creates a new Guest with mapped propertiesGuest guest = dto.ToEntity();ID properties are excluded from ToEntity() by default. Add [MapProperty] (with no arguments) to the ID property to force inclusion.
Accessibility
Section titled “Accessibility”The generated extension class matches the DTO’s accessibility:
| DTO Accessibility | Extensions Class |
|---|---|
public | public static |
internal | internal static |
Supported Type Kinds
Section titled “Supported Type Kinds”| DTO Type | [MapFrom] | [MapTo] | [GenerateProjection] |
|---|---|---|---|
class | Yes | Yes | Yes |
record | Yes | Yes | Yes |
struct | Yes | Planned | Planned |
record struct | Yes | Planned | Planned |
Next Steps
Section titled “Next Steps”- Projections Guide — SQL-translatable Expression mappings for EF Core
- Custom Converters Guide —
IValueConverter<TSource, TTarget>for complex types - Feature Matrix — Complete feature comparison table