✨ Roslyn source generator for compile-time DTO mapping

AutoMap.Generator
Compile-time object mapping for .NET

Add [Map(typeof(OrderDto))] to your domain class and get a generated ToOrderDto() extension method — zero reflection, AOT-safe, and ready at build time.

$ dotnet add package AutoMap.Generator
your model
using AutoMap;

[Map(typeof(OrderDto))]
public class Order
{
    public int Id { get; set; }
    public string CustomerName { get; set; } = "";
    public decimal Total { get; set; }
}
generated at build time
public static class AutoMapExtensions
{
    public static OrderDto ToOrderDto(this Order source)
        => new()
        {
            Id = source.Id,
            CustomerName = source.CustomerName,
            Total = source.Total,
        };
}

Everything you need

Convention-first mapping with generated extensions, collection helpers, and Roslyn diagnostics.

Zero Reflection

Mapping code is emitted at compile time, not discovered at runtime. Plain generated C# with no reflection or expression compilation.

🧊

AOT-Safe

Works with .NET Native AOT and Blazor WebAssembly. No runtime scanning to break trimming or ahead-of-time compilation.

🧭

Convention-Based

Properties are matched by name automatically, so the common case stays zero-config and easy to read.

🪆

Nested Mapping

When nested source types also have [Map], AutoMap chains the generated calls for you automatically.

📚

Collection Helpers

Generates companion ToXDtos(IEnumerable<X>) helpers so single-object and collection mapping stay consistent.

💡

Roslyn Code Fix

Type mismatch? The IDE lightbulb can add [MapIgnore] for you when a property should be skipped.

Examples

Build-time mapping you can read, debug, and navigate in the IDE.

using AutoMap;

[Map(typeof(OrderDto))]
public class Order
{
    public int Id { get; set; }
    public string CustomerName { get; set; } = "";
    public decimal Total { get; set; }
}

public class OrderDto
{
    public int Id { get; set; }
    public string CustomerName { get; set; } = "";
    public decimal Total { get; set; }
}

// Generated — use it like this:
var dto = order.ToOrderDto();
var dtos = orders.ToOrderDtos(); // collection helper
[Map(typeof(AddressDto))]
public class Address
{
    public string Street { get; set; } = "";
    public string City { get; set; } = "";
}

[Map(typeof(CustomerDto))]
public class Customer
{
    public string Name { get; set; } = "";
    public Address HomeAddress { get; set; } = new();
}

// AutoMap chains them automatically:
var dto = customer.ToCustomerDto();
// dto.HomeAddress is AddressDto — generated null-safe call
[Map(typeof(ProductDto))]
public class Product
{
    public int Id { get; set; }
    public string InternalCode { get; set; } = ""; // rename

    [MapProperty("InternalCode")]   // map to different source property
    public string Sku { get; set; } = "";

    [MapIgnore]                     // skip this property
    public string Secret { get; set; } = "";

    [MapWith("source.Price * 1.2m")] // custom expression
    public decimal PriceWithVat { get; set; }
}
// AutoMap generates a mapper class you can inject:
services.AddSingleton<IAutoMapper<Order, OrderDto>>(
    AutoMapExtensions.OrderToOrderDtoMapper.Instance);

// Inject and use:
public class OrderService(IAutoMapper<Order, OrderDto> mapper)
{
    public OrderDto GetDto(Order order) => mapper.Map(order);
}
[Map(typeof(OrderDto), GenerateProjection = true)]
public class Order
{
    public int Id { get; set; }
    public string CustomerName { get; set; } = "";
    public decimal Total { get; set; }
}

public class OrderDto
{
    public int Id { get; set; }
    public string CustomerName { get; set; } = "";
    public decimal Total { get; set; }
}

// Generated: an Expression<Func<Order, OrderDto>> plus a
// ProjectToOrderDto() IQueryable extension — translated by EF Core,
// so only the mapped columns are selected in SQL.
var dtos = dbContext.Orders
    .Where(o => o.Total > 100)
    .ProjectToOrderDto()
    .ToList();

Try it in 30 seconds

No new project, no clone required — paste this into a single .cs file and run it with the .NET SDK's file-based apps (.NET 10+), or scaffold the full demo with dotnet new.

Option A — single file, zero setup

// Save as automap-try.cs, then run: dotnet run automap-try.cs
#:package AutoMap.Generator@1.*

using AutoMap;

[Map(typeof(OrderDto))]
public class Order
{
    public int Id { get; set; }
    public string Customer { get; set; } = "";
}

public class OrderDto
{
    public int Id { get; set; }
    public string Customer { get; set; } = "";
}

var order = new Order { Id = 1, Customer = "Ada" };
Console.WriteLine(order.ToOrderDto().Customer); // "Ada"

Option B — full runnable demo project

dotnet new install AutoMap.Generator.Templates
dotnet new automap-demo -o MyAutoMapDemo
cd MyAutoMapDemo
dotnet run

Scaffolds a console app exercising every attribute — flattening, enums, reverse mapping, projections, and more. See templates/ for source.

Build-time diagnostics

Problems surfaced during compilation — before they become runtime surprises.

Code Severity Description
AM001 ⛔ Error Destination type not found
AM002 ⛔ Error Destination has no settable properties matching source
AM003 ⚠ Warning Property type mismatch — consider [MapWith]
AM004 ⚠ Warning Property skipped due to incompatible type — use [MapIgnore] or add [Map] on source type. IDE lightbulb available.
AM005 ⛔ Error [MapWith] expression cannot be blank
AM006 ⚠ Warning Circular mapping detected
AM007 ⚠ Warning Strict mode: unmapped destination property
AM008 ⚠ Warning [GenerateProjection] requested but mapping uses constructs unsupported in expression trees (e.g. ?., switch) — projection skipped

How it compares

Designed for compile-time mapping, runtime simplicity, and AOT-friendly applications.

Feature AutoMap.Generator AutoMapper Mapster
Compile-time generated
Zero reflection
AOT / NativeAOTPartial
IDE navigation to mapping code
Roslyn diagnostics
Runtime config
Projection (IQueryable)

Also by the same author