📖 Documentation site · NuGet · Changelog · Migrate from AutoMapper · Benchmarks
Compile-time object mapping for .NET via Roslyn source generators.
Add [Map(typeof(OrderDto))] to your class — AutoMap generates a strongly-typed ToOrderDto() extension method at build time. No reflection. No runtime overhead. AOT-safe.
- Performance
- Why AutoMap.Generator over Mapperly?
- Installation
- Try it in 30 seconds
- Migrating from AutoMapper
- Quick start
- Controlling properties
- Nested object mapping
- Collection mapping
- Reverse mapping
IAutoMapper<TSource, TResult>interface[MapWith]— custom expression[MapWhen]— conditional mapping[TrimStrings]— string sanitisation[MapFormat]— formatting shorthandIMapFrom<T>— convention-based mapping- Partial method hooks
Strict = true— compile-time enforcement- Enum mapping
- Flattening
[MapDefault]— null substitution- Constructor mapping
- IQueryable projection —
GenerateProjection - Property matching rules
- Attribute reference
- Diagnostics
- Records and structs
- FAQ
[Map(typeof(OrderDto))]publicclassOrder{publicintId{get;set;}publicstringCustomer{get;set;}="";publicdecimalTotal{get;set;}}// Generated automatically:publicstaticpartialclassAutoMapExtensions{publicstaticOrderDtoToOrderDto(thisOrdersrc){if(srcisnull)thrownewArgumentNullException(nameof(src));returnnewOrderDto{Id=src.Id,Customer=src.Customer,Total=src.Total,};}}// Usage:vardto=order.ToOrderDto();AutoMap.Generator generates the same code a developer would write by hand — there is no runtime overhead beyond the property assignments themselves.
| Method | Mean | Ratio | Alloc |
|---|---|---|---|
| Hand-written | 7.23 ns | 1.00 | 64 B |
| AutoMap.Generator | 6.64 ns | 0.93 | 64 B |
| Mapperly | 6.68 ns | 0.93 | 64 B |
| AutoMapper | 53.40 ns | 7.45x | 64 B |
Flat 5-property mapping, BenchmarkDotNet on Windows 11 / AMD Ryzen 9 5900X / .NET 9.0.18. A nested-object + 10-item collection scenario shows the same pattern (AutoMap 105.8 ns vs AutoMapper 234.2 ns, a 2.07x gap) since AutoMapper's reflection overhead scales with mapping complexity. Run
dotnet run -c Release -- --filter '*'inbenchmarks/to reproduce both scenarios.
Both are Roslyn source generators with identical runtime performance. The key differences are in the developer experience:
| AutoMap.Generator | Mapperly | |
|---|---|---|
| Configuration style | Attribute on the class ([Map]) | Separate mapper class ([Mapper] partial class) |
| Setup needed | None — extension methods, no setup | One mapper class per mapping group |
| AOT / MAUI | ✅ | ✅ |
| Reverse mapping | Reverse = true in the attribute | [MapperIgnoreSource] + manual reverse method |
| Custom expressions | [MapWith("src.Price.ToString(\"C2\")")] | [MapProperty(Use = nameof(...))] |
| Conditional mapping | [MapWhen("src.IsActive")] | Manual partial method |
| Build-time diagnostics | AM001–AM011 | Yes |
| Migration guide | AutoMapper → AutoMap | — |
AutoMap.Generator is the better fit when you want zero setup — just annotate your domain class and use the generated extension method. No extra mapper classes, no DI registration needed.
dotnet add package AutoMap.Generator
Targets netstandard2.0 — works with .NET 6, 7, 8, 9, and MAUI.
Option A — single file, zero setup (requires .NET 10 SDK's file-based apps):
// Save as automap-try.cs, then run: dotnet run automap-try.cs
#:packageAutoMap.Generator@1.*usingAutoMap;[Map(typeof(OrderDto))]publicclassOrder{publicintId{get;set;}publicstringCustomer{get;set;}="";}publicclassOrderDto{publicintId{get;set;}publicstringCustomer{get;set;}="";}varorder=newOrder{Id=1,Customer="Ada"};Console.WriteLine(order.ToOrderDto().Customer);// "Ada"Option B — full runnable demo project exercising every attribute (flattening, enums, reverse mapping, projections, and more):
dotnet new install AutoMap.Generator.Templates
dotnet new automap-demo -o MyAutoMapDemo
cd MyAutoMapDemo
dotnet runSee templates/ for the template source, or run it straight from a clone without installing anything:
git clone https://github.com/Swevo/AutoMap.Generator.git
cd AutoMap.Generator/templates/content/AutoMap.Demo
dotnet runAutoMap.Generator now ships an AM009 analyzer + code fix to remove the most repetitive part of AutoMapper migrations.
When the analyzer sees an AutoMapper-style CreateMap<TSource, TDest>() call, it reports an informational suggestion at the call site:
CreateMap<Order,OrderDto>();// ℹ AM009: 'CreateMap<Order, OrderDto>()' can be migrated to AutoMap —// add [Map(typeof(OrderDto))] to 'Order' instead.Apply the lightbulb and AutoMap.Generator will add the attribute to the source type for you — even when the source type lives in a different file in the same project:
[Map(typeof(OrderDto))]publicclassOrder{publicintId{get;set;}}Current scope:
- Detects
CreateMap<TSource, TDest>()calls from AutoMapper-style APIs - Adds
[Map(typeof(TDest))]to the source type when that type is available in source - Leaves the original AutoMapper configuration in place for manual cleanup, so the fix does not silently remove custom profile logic
- Does not yet translate fluent member configuration such as
.ForMember(...),.Ignore(),.MapFrom(...),.Condition(...), or.ReverseMap()— use the existing migration guide in MIGRATION.md for those patterns
This analyzer works from method/type names and does not require a reference to the real AutoMapper NuGet package in order to function in tests or custom tooling scenarios.
Place [Map(typeof(Destination))] on the class you want to map from:
usingAutoMap;[Map(typeof(UserDto))]publicclassUser{publicintId{get;set;}publicstringEmail{get;set;}="";publicstringPasswordHash{get;set;}="";// no matching dest → silently omitted}publicclassUserDto{publicintId{get;set;}publicstringEmail{get;set;}="";}Generated: user.ToUserDto()
Place [MapFrom(typeof(Source))] on the DTO when you want to keep source types clean:
usingAutoMap;publicclassOrder{publicintId{get;set;}publicstringCustomer{get;set;}="";}[MapFrom(typeof(Order))]publicclassOrderDto{publicintId{get;set;}publicstringCustomer{get;set;}="";}Generated: order.ToOrderDto() — extension method is on Order, returning OrderDto.
Both directions work. Stack [Map] for multiple destinations:
[Map(typeof(OrderDto))][Map(typeof(OrderSummary))]publicclassOrder{ ...}[Map(typeof(OrderDto),MethodName="AsDto")]publicclassOrder{ ...}// Generated:order.AsDto()[Map(typeof(OrderDto),Reverse=true)]publicclassOrder{ ...}// Generated both:order.ToOrderDto()
dto.ToOrder()[MapFrom(typeof(Order))]publicclassOrderDto{publicintId{get;set;}[MapIgnore]publicstringInternalNote{get;set;}="";// ← never mapped}publicclassOrder{publicstringCustomerName{get;set;}="";}[MapFrom(typeof(Order))]publicclassOrderDto{[MapProperty("CustomerName")]publicstringClient{get;set;}="";// Generated: Client = src.CustomerName}When a destination property type differs from the source, AutoMap.Generator checks whether a [Map] relationship exists between the two types and emits a null-safe chained call automatically:
[Map(typeof(AddressDto))]publicclassAddress{publicstringCity{get;set;}="";}[Map(typeof(OrderDto))]publicclassOrder{publicintId{get;set;}publicAddress?Address{get;set;}}publicclassOrderDto{publicintId{get;set;}publicAddressDto?Address{get;set;}}// Generated:returnnewOrderDto{Id=src.Id,Address=src.Address?.ToAddressDto(),// ← resolved automatically};No configuration needed — as long as the [Map] for the nested type exists anywhere in the compilation, AutoMap.Generator wires it up.
List<T>, T[], IEnumerable<T>, ICollection<T>, and other standard collection types are mapped automatically when the element type has a registered [Map]:
[Map(typeof(ItemDto))]publicclassItem{publicintId{get;set;}}[Map(typeof(OrderDto))]publicclassOrder{publicintId{get;set;}publicList<Item>Items{get;set;}=new();}publicclassOrderDto{publicintId{get;set;}publicList<ItemDto>Items{get;set;}=new();}// Generated (using System.Linq added automatically):returnnewOrderDto{Id=src.Id,Items=src.Items?.Select(x =>x.ToItemDto()).ToList(),};| Source collection | Destination collection | Emitted expression |
|---|---|---|
List<T> / IEnumerable<T> / ICollection<T> | List<TDto> | .Select(x => x.To...()).ToList() |
T[] | TDto[] | .Select(x => x.To...()).ToArray() |
Set Reverse = true on [Map] or [MapFrom] to generate both directions at once:
[Map(typeof(OrderDto),Reverse=true)]publicclassOrder{publicintId{get;set;}publicstringCustomer{get;set;}="";}publicclassOrderDto{publicintId{get;set;}publicstringCustomer{get;set;}="";}// Generated:order.ToOrderDto()// Order → OrderDto (forward)
dto.ToOrder()// OrderDto → Order (reverse)Both directions are registered in the mapping registry, so nested and collection resolution works bidirectionally too.
AutoMap.Generator emits an IAutoMapper<in TSource, out TResult> interface into your compilation alongside a concrete sealed mapper class for every registered mapping:
// Interface (emitted into your compilation automatically):publicinterfaceIAutoMapper<inTSource,outTResult>{TResultMap(TSourcesource);}// For [Map(typeof(OrderDto))] on Order, the following is generated:publicsealedclassOrderToOrderDtoMapper:IAutoMapper<Order,OrderDto>{publicstaticreadonlyOrderToOrderDtoMapperInstance=newOrderToOrderDtoMapper();publicOrderDtoMap(Ordersource)=>source.ToOrderDto();}Use Instance to avoid allocations, or inject IAutoMapper<Order, OrderDto> into your services for testability:
// DI registration:services.AddSingleton<IAutoMapper<Order,OrderDto>>(AutoMapExtensions.OrderToOrderDtoMapper.Instance);// Service:publicclassOrderService(IAutoMapper<Order,OrderDto>mapper){ ...}Use [MapWith] when you need a computed or transformed value rather than a direct property copy. Write any valid C# expression using src to reference the source object:
publicclassOrder{publicintId{get;set;}publicdecimalPrice{get;set;}publicList<string>Tags{get;set;}=new();}[MapFrom(typeof(Order))]publicclassOrderDto{publicintId{get;set;}[MapWith("src.Price.ToString(\"C2\")")]publicstringPriceFormatted{get;set;}="";[MapWith("src.Tags.Count")]publicintTagCount{get;set;}[MapWith("src.Id > 1000 ? \"Premium\" : \"Standard\"")]publicstringTier{get;set;}="";}Generated:
returnnewOrderDto{Id=src.Id,PriceFormatted=src.Price.ToString("C2"),TagCount=src.Tags.Count,Tier=src.Id>1000?"Premium":"Standard",};[MapWith] does not require a source property with a matching name — it is injected verbatim. If both [MapWith] and [MapIgnore] are on the same property, [MapIgnore] wins.
Place [MapWhen("condition")] on a destination property to wrap the assignment in a compile-time ternary. The property is mapped when condition is true; otherwise Fallback (default: default) is used.
publicclassOrder{publicboolIsPremium{get;set;}publicstringTag{get;set;}="";publicdecimalPrice{get;set;}}[MapFrom(typeof(Order))]publicclassOrderDto{// Map only when active, fall back to default[MapWhen("src.IsPremium")]publicstringTag{get;set;}="";// Generated: Tag = src.IsPremium ? src.Tag : default,// Custom fallback value[MapWhen("src.IsPremium",Fallback="\"Standard\"")]publicstringTier{get;set;}="";// Generated: Tier = src.IsPremium ? src.Tier : "Standard",// Combine with [MapWith] — the custom expression becomes the true branch[MapWhen("src.IsPremium")][MapWith("src.Price.ToString(\"C2\")")]publicstringPriceLabel{get;set;}="";// Generated: PriceLabel = src.IsPremium ? src.Price.ToString("C2") : default,// Also works with flattening[MapWhen("src.IsPremium",Fallback="\"Guest\"")]publicstringCustomerName{get;set;}="";// Generated: CustomerName = src.IsPremium ? src.Customer?.Name : "Guest",}[MapIgnore] takes precedence when both attributes are on the same property.
Place [TrimStrings] on the class decorated with [Map] or [MapFrom] to automatically wrap every mapped string property with ?.Trim(). Ideal for user input, CSV imports, or data coming from external APIs.
[Map(typeof(OrderDto))][TrimStrings]publicclassOrder{publicstringName{get;set;}="";publicstringTag{get;set;}="";publicintId{get;set;}}// Generated:returnnewOrderDto{Name=src.Name?.Trim(),// ← trimmedTag=src.Tag?.Trim(),// ← trimmedId=src.Id,// ← non-string: unchanged};[TrimStrings] can be placed on either the source or the destination type. [MapWith] still takes per-property precedence.
Use [MapFormat] when you want to format a source value as a string. It generates .ToString("format") (or ?.ToString("format") for reference/nullable types) without needing a [MapWith] expression. Works across type boundaries (e.g. decimal → string).
publicclassOrder{publicdecimalPrice{get;set;}publicDateTime?ShippedAt{get;set;}}[MapFrom(typeof(Order))]publicclassOrderDto{[MapFormat("C2")]publicstringPrice{get;set;}="";// → src.Price.ToString("C2")[MapFormat("yyyy-MM-dd")]publicstringShippedAt{get;set;}="";// → src.ShippedAt?.ToString("yyyy-MM-dd")}Composes with [MapWhen]:
[MapFormat("yyyy-MM-dd")][MapWhen("src.IsShipped",Fallback="\"N/A\"")]publicstringShippedAt{get;set;}= "";// Generated: ShippedAt = src.IsShipped ? src.ShippedAt.ToString("yyyy-MM-dd") : "N/A",Implement AutoMap.IMapFrom<TSource> on a DTO to register the mapping without any attribute. Equivalent to [MapFrom(typeof(TSource))]. Deduplicates automatically if both are present.
publicclassOrderDto:IMapFrom<Order>{publicintId{get;set;}publicstringName{get;set;}="";}// Automatically generates: order.ToOrderDto()// No attribute needed on Order or OrderDto.Every generated mapping method stores the mapped object in a local variable and then calls a static partial void On{MethodName}(TSource src, TDest result) before returning. Implement the partial method in your own companion file for post-mapping logic. The call is compiled away at zero cost if you don't implement it.
// AutoMap generates:publicstaticOrderDtoToOrderDto(thisOrdersrc){if(srcisnull)thrownewArgumentNullException(nameof(src));varresult=newOrderDto{Id=src.Id,Name=src.Name};OnToOrderDto(src,result);// ← you implement this (optional)returnresult;}staticpartialvoidOnToOrderDto(global::MyApp.Ordersrc,global::MyApp.OrderDtoresult);// Your code (in your own partial class):namespaceAutoMap{publicstaticpartialclassAutoMapExtensions{staticpartialvoidOnToOrderDto(Ordersrc,OrderDtoresult){result.MappedAt=DateTime.UtcNow;}}}Add Strict = true to [Map] or [MapFrom] to turn mapping warnings into errors. AM001 (no properties mapped) and AM004 (type incompatibility) are promoted from warnings to errors:
[Map(typeof(OrderDto),Strict=true)]publicclassOrder{/* ... */}// Any unresolvable property → build error, not warningWhen source and destination properties are different enum types, AutoMap.Generator generates a compile-time switch expression mapping values by name automatically:
publicenumOrderStatus{Pending,Active,Cancelled}publicenumOrderStatusDto{Pending,Active,Cancelled}[Map(typeof(OrderDto))]publicclassOrder{publicOrderStatusStatus{get;set;}}publicclassOrderDto{publicOrderStatusDtoStatus{get;set;}}// Generated:Status=src.Statusswitch{global::MyApp.OrderStatus.Pending=>global::MyApp.OrderStatusDto.Pending,global::MyApp.OrderStatus.Active=>global::MyApp.OrderStatusDto.Active,global::MyApp.OrderStatus.Cancelled=>global::MyApp.OrderStatusDto.Cancelled,
_ =>default},Same-type enum properties are mapped directly (Status = src.Status) — no switch needed.
Place [MapEnum] on a source enum member to redirect it to a differently-named destination member:
publicenumSrcStatus{[MapEnum("Running")]// ← maps to DstStatus.RunningActive,Done}publicenumDstStatus{Running,Done}When a source enum member has no matching destination member and no [MapEnum] redirect, AM006 is reported and the _ => default fallback is used so the build succeeds:
// ⚠ AM006: Source enum member 'Unknown' on 'SrcStatus' has no matching member in 'DstStatus'.publicenumSrcStatus{Active,Unknown}publicenumDstStatus{Active}// ← no Unknown// Generated: _ => default (covers Unknown at runtime)When a destination property has no direct source match, AutoMap.Generator automatically tries to resolve it by splitting the name at PascalCase boundaries and walking the source type tree — up to 3 levels deep.
publicclassAddress{publicstringCity{get;set;}="";}publicclassCustomer{publicAddress?Address{get;set;}publicstringName{get;set;}="";}publicclassOrder{publicintId{get;set;}publicCustomer?Customer{get;set;}}[MapFrom(typeof(Order))]publicclassOrderDto{publicintId{get;set;}// direct matchpublicstringCustomerName{get;set;}="";// → src.Customer?.NamepublicstringCustomerAddressCity{get;set;}="";// → src.Customer?.Address?.City}Generated:
returnnewOrderDto{Id=src.Id,CustomerName=src.Customer?.Name,CustomerAddressCity=src.Customer?.Address?.City,};Rules:
- Direct name matches always take priority over flattening
- Value-type intermediates use
.instead of?.(structs can't be null) - Flattening is attempted before
AM004is reported
Place [MapDefault("expression")] on any destination property to substitute the provided expression when the source value is null. The expression is appended as ?? expr and works with both direct and flattened paths:
publicclassOrder{publicstring?Region{get;set;}publicCustomer?Customer{get;set;}}[MapFrom(typeof(Order))]publicclassOrderDto{[MapDefault("\"Global\"")]publicstringRegion{get;set;}="";// → src.Region ?? "Global"[MapDefault("\"Guest\"")]publicstringCustomerName{get;set;}="";// → src.Customer?.Name ?? "Guest"[MapDefault("0")]publicintCustomerOrderCount{get;set;}// → src.Customer?.OrderCount ?? 0}[MapIgnore] takes precedence when both are on the same property. [MapDefault] has no effect on [MapWith] — write the full expression there instead.
AutoMap.Generator automatically detects when the destination type has no public parameterless constructor and switches to constructor-call syntax — no configuration needed.
// Destination: positional record (no parameterless ctor)publicrecordOrderDto(intId,stringCustomer);[Map(typeof(OrderDto))]publicclassOrder{publicintId{get;set;}publicstringCustomer{get;set;}="";}// Generated:returnnewglobal::MyApp.OrderDto(src.Id,src.Customer);Parameter names are matched to source properties case-insensitively.
Use [MapConstructor] on the destination type to force constructor mapping even when a parameterless constructor exists, or to select the primary constructor among several:
[MapConstructor]// ← force ctor mappingpublicclassOrderDto{publicintId{get;}publicstringName{get;}publicOrderDto(){}// parameterless exists, but ignoredpublicOrderDto(intid,stringname){ ...}// ← selected (longest)}[Map(typeof(OrderDto))]publicclassOrder{publicintId{get;set;}publicstringName{get;set;}="";}// Generated:returnnewglobal::MyApp.OrderDto(src.Id,src.Name);When the selected constructor covers only some properties, remaining writable properties are mapped in an object-initializer block:
publicclassOrderDto{publicintId{get;}publicstringTag{get;set;}="";publicOrderDto(intid){Id=id;}}// Generated:returnnewglobal::MyApp.OrderDto(src.Id){Tag=src.Tag,};If a constructor parameter has no matching source property, AM005 is reported and default is emitted so the build still succeeds:
// ⚠ AM005: Constructor parameter 'Missing' on 'OrderDto' has no matching property on 'Order'.publicrecordOrderDto(intId,stringMissing);[Map(typeof(OrderDto))]publicclassOrder{publicintId{get;set;}}// Generated: new OrderDto(src.Id, default)Add GenerateProjection = true to [Map]/[MapFrom] to also generate a static Expression<Func<TSource, TDest>> plus an IQueryable<TDest> extension method — the equivalent of AutoMapper's ProjectTo<T>(). EF Core (or any IQueryable provider) can translate the expression directly into the query, so only the columns you actually map are selected from the database:
publicclassOrderDto{publicintId{get;set;}publicstringCustomer{get;set;}="";}[Map(typeof(OrderDto),GenerateProjection=true)]publicclassOrder{publicintId{get;set;}publicstringCustomer{get;set;}="";publicstringInternalNotes{get;set;}="";}// Generated:publicstaticreadonlyExpression<Func<Order,OrderDto>>ToOrderDtoExpression= src =>newOrderDto{Id=src.Id,Customer=src.Customer,};publicstaticIQueryable<OrderDto>ProjectToOrderDto(thisIQueryable<Order>source)=>source.Select(ToOrderDtoExpression);// Usage — EF Core only selects Id and Customer from the database, never InternalNotes:vardtos=awaitdbContext.Orders.ProjectToOrderDto().ToListAsync();C# expression trees cannot contain the null-conditional operator (?.) or a switch expression (compiler restriction — CS8072/CS8829). Constructs that would need either of these are not eligible for projection:
- Nested object mapping and collection mapping (both use
?.internally) [TrimStrings](usessrc.Prop?.Trim())- Automatic flattening through a nullable reference path (e.g.
CustomerName→src.Customer?.Name) - Enum mapping (uses a
switchexpression)
When a mapping requests GenerateProjection = true but contains one of these constructs, AM008 is reported and no projection expression is emitted — the regular instance ToXxx() extension method is unaffected either way. [MapWith], [MapDefault] (??), [MapWhen] (?:), [MapFormat] (when not nullable), constructor mapping, and plain property-to-property copies are all fully supported.
| Rule | Behaviour |
|---|---|
| Name match | Case-insensitive name comparison |
| Type match | Source type must be identical or implicitly convertible to destination type |
[MapIgnore] on dest | Property is skipped |
[MapProperty("X")] on dest | Looks up X on the source instead |
| Readonly dest | Properties with no public setter/init are skipped |
| Static/indexer | Always skipped |
| Inherited properties | Source and destination inheritance chains are walked |
Nested object mapping and collection mapping are resolved automatically when the related
[Map]exists anywhere in the compilation.
| Property | Type | Description |
|---|---|---|
| (constructor) | Type | Destination type ([Map]) or source type ([MapFrom]) |
MethodName | string? | Override the generated method name. Default: To{TypeName} |
Reverse | bool | Also generate the opposite-direction mapping. Default: false |
Strict | bool | Unmapped/incompatible properties become build errors instead of warnings. Default: false |
GenerateProjection | bool | Also generate an Expression<Func<TSource,TDest>> + IQueryable<TDest> projection helper for EF Core. Default: false |
| Property | Type | Description |
|---|---|---|
| (constructor) | string | Name of the source property to read from |
| Property | Type | Description |
|---|---|---|
| (constructor) | string | C# expression using src as the source variable; emitted verbatim as the property assignment RHS |
| Property | Type | Description |
|---|---|---|
| (constructor) | string | C# boolean expression; when true the property maps normally, when false Fallback is used |
Fallback | string? | C# expression for the false branch. Default: default |
| Property | Type | Description |
|---|---|---|
| (constructor) | string | C# expression appended as ?? expr after the source value; applied to direct and flattened paths |
No properties — applies to any destination property to exclude it from all mappings.
AutoMap.Generator ships eleven built-in diagnostics that surface problems at build time.
| ID | Severity | Meaning |
|---|---|---|
| AM001 | ⚠ Warning | No properties matched between source and destination — the mapping would be empty |
| AM002 | ❌ Error | [MapProperty("X")] references a source property that does not exist |
| AM003 | ❌ Error | The type passed to [Map] or [MapFrom] could not be resolved |
| AM004 | ⚠ Warning | A destination property with a matching name was skipped — incompatible types with no registered mapping |
| AM005 | ⚠ Warning | A required constructor parameter has no matching source property — default is emitted. IDE code fix: add [property: MapProperty("X")] to the closest-matching source property (when one can be found) |
| AM006 | ⚠ Warning | A source enum member has no matching destination enum member — _ => default fallback used. IDE code fix: add [MapEnum("X")], offered once per destination member |
| AM007 | ⚠ Warning | Reverse = true was requested, but no reverse properties could be generated |
| AM008 | ⚠ Warning | GenerateProjection = true requested, but the mapping needs ?. or a switch expression — not supported in Expression<Func<,>>. No projection is emitted for this mapping. IDE code fix: remove GenerateProjection = true |
| AM009 | ℹ Info | AutoMapper CreateMap<TSource, TDest>() can be migrated to AutoMap with a [Map(typeof(TDest))] attribute |
| AM010 | ℹ Info | The generated mapping method for this [Map]/[MapFrom] attribute does not appear to be referenced anywhere in the project (as a call, nameof(...), or DI registration) — diagnostic only, no automatic fix, since it may be used via reflection or another project |
| AM011 | 🔕 Hidden | A one-line IDE-only preview of the generated method's signature and its flattened/defaulted/ignored/custom property categories — visible via hover/lightbulb without opening the .g.cs file |
All diagnostic messages include a concrete, ready-to-paste fix snippet (e.g. [MapIgnore], [MapProperty("X")], [MapEnum("X")]) rather than just describing the problem. AM004, AM005, AM006 and AM008 are also re-reported by AutoMapAnalyzer with real source locations (on the property, constructor parameter, enum member, or [Map]/[MapFrom] attribute respectively) so IDE lightbulb code fixes are available for all four. AM010 and AM011 run as standalone analyzers (AutoMapUnusedMappingAnalyzer and AutoMapPreviewAnalyzer) that always report on the [Map]/[MapFrom] attribute itself.
// ⚠ AM001: Mapping from 'Order' to 'ProductDto' produced no property matches.[Map(typeof(ProductDto))]publicclassOrder{publicintId{get;set;}}publicclassProductDto{publicstringSku{get;set;}="";}// ← no common names// ✅ Fix: ensure source and destination share property names, or use [MapProperty].// ❌ AM002: [MapProperty("Foo")] on 'OrderDto.Name' references a property// that does not exist on source type 'Order'.publicclassOrder{publicstringCustomerName{get;set;}="";}[MapFrom(typeof(Order))]publicclassOrderDto{[MapProperty("Foo")]// ← typo!publicstringName{get;set;}="";}// ✅ Fix:[MapProperty("CustomerName")]publicstringName{get;set;}= "";Structs — the null-guard is omitted since value types can't be null:
[Map(typeof(PointDto))]publicstructPoint{publicintX{get;set;}publicintY{get;set;}}// Generated: return new PointDto { X = src.X, Y = src.Y }; ← no null checkRecords — init-only properties work out of the box with object initialiser syntax. Positional records (primary constructor parameters) require the destination type to have either a parameterless constructor or explicit init properties:
// ✅ Works — standard record with init propertiespublicrecordOrderDto{publicintId{get;init;}publicstringName{get;init;}="";}[Map(typeof(OrderDto))]publicclassOrder{publicintId{get;set;}publicstringName{get;set;}="";}Q: Does AutoMap support collection properties (List<T>, arrays)?
Yes — List<T>, T[], IEnumerable<T>, and ICollection<T> are mapped automatically when the element type has a [Map] relationship. See the Collection mapping section.
Q: Can I map to a type in a different assembly?
Yes. The destination type just needs to be accessible (public, or internal with InternalsVisibleTo).
Q: Does it work with nullable reference types?
Yes. string → string? (and vice versa) maps correctly since the underlying type is the same.
Q: Is it AOT-safe? Yes. All code is generated at build time — zero reflection at runtime.
Q: Why not just use AutoMapper?
AutoMapper is powerful but relies on runtime reflection, is not AOT-safe, and requires a MapperConfiguration setup. AutoMap is a build-time generator: if it compiles, it maps correctly.
Q: Why not just use Mapperly?
Mapperly is excellent and shares the same zero-overhead goal. AutoMap.Generator takes a different ergonomic approach: annotate the class directly ([Map(typeof(Dto))]) rather than creating a separate mapper class. This means no boilerplate mapper files, no DI setup, and a one-liner migration path from AutoMapper. See the full comparison table above.
🌐 Full suite overview: swevo.github.io
| Package | Description |
|---|---|
| AutoWire | Compile-time DI auto-registration — [Scoped]/[Singleton]/[Transient] generates IServiceCollection code. Zero reflection. |
| AutoValidate.Generator | Compile-time FluentValidation wiring — discovers AbstractValidator<T> subclasses and generates AddValidators(). |
| AutoResult.Generator | Compile-time Result<T> monad — [TryWrap] generates Try*() wrappers for sync, async and void methods. |
| AutoQuery.Generator | Compile-time LINQ query specs — [QuerySpec(typeof(T))] generates Apply(IQueryable<T>). |
| AutoDispatch.Generator | Compile-time CQRS dispatcher — [Handler] generates a strongly-typed IDispatcher. No IRequest<T>, no reflection. |
| AutoLog.Generator | Compile-time high-performance logging — [Log(Level, Message)] on a partial method generates LoggerMessage.Define. AOT-safe. |
| AutoHttpClient.Generator | Compile-time typed HTTP client — [HttpClient] on an interface generates a strongly-typed client. AOT-safe Refit alternative. |
Issues and PRs welcome at github.com/Swevo/AutoMap.Generator.
| Package | Downloads | Description |
|---|---|---|
| AutoWire | Compile-time dependency injection auto-registration for | |
| AutoQuery.Generator | Compile-time query composition for IQueryable using Roslyn incremental source generators | |
| AutoArchitecture | Compile-time architecture/dependency-rule enforcement for | |
| AutoHttpClient.Generator | Compile-time typed HTTP client generation for | |
| AutoDispatch.Generator | Compile-time CQRS dispatcher for | |
| AutoLog.Generator | Compile-time high-performance logging for | |
| AutoValidate.Generator | Compile-time FluentValidation wiring for |
MIT