Start Debugging

AutoMapper vs Mapperly vs handgeschriebenes Mapping in 2026

Mapperly ist die Standardwahl für neuen .NET-Code: gleiche Geschwindigkeit wie handgeschriebenes Mapping, funktioniert unter Native AOT und meldet nicht gemappte Member zur Compile-Zeit. AutoMapper gewinnt weiterhin bei ProjectTo. Mit Benchmarks und Lizenzschwellen.

Für neuen .NET-Code in 2026 sollten Sie Mapperly verwenden. Es erzeugt einfaches C# zur Compile-Zeit, liegt innerhalb von 3% des handgeschriebenen Mappings, veröffentlicht sauber unter Native AOT und macht aus einer vergessenen Eigenschaft eine Compiler-Diagnose statt eines stillen leeren Strings. Schreiben Sie das Mapping von Hand, wenn ein Projekt weniger als etwa zwanzig Maps hat oder Quell- und Zielstruktur wirklich auseinandergehen. Bleiben Sie bei AutoMapper nur dann, wenn ProjectTo in einer großen EF-Core-Codebasis tragend ist und Sie sich für die kostenlose Community-Stufe qualifizieren, denn oberhalb von 5.000.000 USD Jahresumsatz macht die Lizenz aus der Entscheidung eine Bestellung.

Alle Zahlen unten wurden auf einem Apple M4 (10 Kerne) mit .NET SDK 10.0.302 und Zielframework net10.0 gemessen, mit AutoMapper 16.2.0 (veröffentlicht am 2026-07-02), Riok.Mapperly 4.3.1 (veröffentlicht am 2025-12-22) und BenchmarkDotNet 0.15.8.

Die Matrix

AutoMapper 16.2.0Mapperly 4.3.1Handgeschrieben
LizenzRPL-1.5 Copyleft oder kommerziell kostenpflichtigApache 2.0keine
Kosten oberhalb 5.000.000 USD Umsatz799 bis 6.399 USD pro Jahrkostenloskostenlos
Wie das Mapping entstehtReflection plus kompilierte Expression Trees beim ersten AufrufRoslyn Source Generator zur Compile-ZeitSie
Nicht gemappter Ziel-Memberstill, nur AssertConfigurationIsValid() findet ihnWarnung RMG012, zu Fehler eskalierbarder Compiler sagt ebenfalls nichts
Nicht gemappter Quell-Memberwird gar nicht gemeldetWarnung RMG020wird nicht gemeldet
Native-AOT-VeröffentlichungIL2104 plus IL3053, stürzt beim Start abnull Warnungen, läuftnull Warnungen, läuft
Kaltkosten des ersten Mappings~33 ms für 3 Maps~1 ms0
Mapping eines Objekts105.79 ns60.44 ns58.48 ns
EF-Core-ProjektionProjectTo mit expliziter Erweiterung, Parametern und Rekursionstiefegenerierte IQueryable-Projektion, mehrere Funktionen fehlenschreiben Sie das Select
Map(object, type) zur Laufzeitjaneinnein
Debugbare Ausgabekompilierter Expression Treelesbare .g.cs, in die Sie hineinspringen könnenIhr eigener Code

Die Lizenz ist die Achse, an der alles andere hängt

Am 2025-07-02 übertrug Jimmy Bogard AutoMapper und MediatR an Lucky Penny Software und lizenzierte beide neu. AutoMapper 15.0.0 und höher erscheinen unter einem dualen Modell: der Reciprocal Public License 1.5 für Open-Source-Nutzung oder einer kostenpflichtigen kommerziellen Lizenz. Version 14.x und älter bleiben dauerhaft unter MIT.

RPL-1.5 ist nicht MIT mit Zusatzschritten. Es ist ein starkes reziprokes Copyleft, das auch bereitgestellte Software erfasst, nicht nur verteilte, deshalb können kommerzielle Closed-Source-Produkte realistisch nicht auf dem RPL-Build ausliefern. Damit bleibt der kommerzielle Vertrag, dessen kostenlose Community-Stufe Organisationen mit weniger als 5.000.000 USD Bruttojahresumsatz abdeckt, die zusätzlich weniger als 10.000.000 USD Fremdkapital aufgenommen haben und keine staatlichen, halbstaatlichen oder Hochschuleinrichtungen sind. Oberhalb dieser Grenze gelten die veröffentlichten Stufen: Standard für 799 USD pro Jahr bei 1 bis 10 Entwicklern, Professional für 1.499 USD pro Jahr bei 11 bis 50 und Enterprise für 6.399 USD pro Jahr bei unbegrenzt vielen Entwicklern. Gezählt werden nur Entwickler, die aktiv Code schreiben oder pflegen, der die Bibliothek aufruft, also ohne QA, Design und Frontend-Arbeit.

Die Durchsetzung ist bewusst weich. Es gibt keinen Lizenzserver, keinen Netzwerkaufruf und keine Funktionssperre. Ein fehlender oder abgelaufener Schlüssel erzeugt eine Log-Meldung und sonst nichts, und seit 16.2.0 kann der Schlüssel statt über cfg.LicenseKey auch aus den Umgebungsvariablen AUTOMAPPER_LICENSE_KEY oder LUCKYPENNY_LICENSE_KEY kommen. Weiche Durchsetzung ist aber nicht dasselbe wie Erlaubnis, und “uns ist keine Warnung in den Logs aufgefallen” ist keine Lizenzposition, die jemand in einer Beschaffungsprüfung verteidigen möchte.

Das ist dieselbe Weggabelung wie bei den Mediator-Bibliotheken, und die Argumentation überträgt sich direkt: siehe MediatR vs einfache Service-Klassen in 2026 für die vollständige Aufschlüsselung der Community-Stufe und der RPL-1.5-Pflichten.

Wann Sie Mapperly wählen sollten

Wann Sie von Hand mappen sollten

Wann Sie bei AutoMapper bleiben sollten

Wenn Sie sich für den Wechsel entscheiden, ist das Vorgehen Schritt für Schritt beschrieben in von AutoMapper zu quellgeneriertem Mapping mit Mapperly migrieren.

Der Benchmark

Das Modell ist ein Order mit fünf skalaren Membern, einem verschachtelten Customer, fünf OrderLine-Kindern und einem Enum, das auf seinen Textnamen gemappt wird. [MemoryDiagnoser], Standard-Job, und die Expression-Kompilierung von AutoMapper wird im [GlobalSetup] vorgewärmt, damit die Messung den Dauerbetrieb abbildet und nicht die Kosten des ersten Aufrufs.

// .NET SDK 10.0.302, net10.0, C# 14
// AutoMapper 16.2.0, Riok.Mapperly 4.3.1, BenchmarkDotNet 0.15.8
[MemoryDiagnoser]
public class MappingBenchmarks
{
    private Order _order = null!;
    private List<Order> _orders = null!;
    private IMapper _autoMapper = null!;
    private OrderMapper _mapperly = null!;

    [GlobalSetup]
    public void Setup()
    {
        _order = MakeOrder(1);
        _orders = Enumerable.Range(1, 1000).Select(MakeOrder).ToList();

        var config = new MapperConfiguration(
            cfg => cfg.AddProfile<OrderProfile>(),
            NullLoggerFactory.Instance);
        _autoMapper = config.CreateMapper();
        _mapperly = new OrderMapper();

        _autoMapper.Map<OrderDto>(_order); // warm the expression compilation
    }

    [Benchmark(Baseline = true)]
    public OrderDto HandWritten_Single() => HandMapper.ToDto(_order);

    [Benchmark]
    public OrderDto Mapperly_Single() => _mapperly.ToDto(_order);

    [Benchmark]
    public OrderDto AutoMapper_Single() => _autoMapper.Map<OrderDto>(_order);
}

Ergebnisse auf einem Apple M4, 10 physische Kerne, .NET 10.0.10 Arm64 RyuJIT:

MethodeMittelwertRatioAllokiertAllokationsverhältnis
HandWritten_Single58.48 ns1.00624 B1.00
Mapperly_Single60.44 ns1.03624 B1.00
AutoMapper_Single105.79 ns1.81704 B1.13
HandWritten_100072,696 ns1.00632,091 B1.00
Mapperly_100077,334 ns1.06672,093 B1.06
AutoMapper_1000103,376 ns1.42720,640 B1.14

Lesen Sie das ehrlich: 45 Nanosekunden pro Objekt sind nicht der Grund zu wechseln. Bei einem Request, der 1.000 Bestellungen mappt, beträgt der gesamte Unterschied 31 Mikrosekunden, was neben einem einzigen Datenbank-Roundtrip nicht auffällt. Das Performance-Argument trägt erst bei sehr hohen Objektzahlen und ist der schwächste der drei Gründe für Mapperly.

Die Lücke von 40.000 Byte zwischen Mapperly und handgeschriebenem Code im Fall mit 1.000 Objekten ist ein realer Effekt, den man verstehen sollte. Mapperly weitet den Parameter eines generierten Mappers für verschachtelte Sammlungen auf IReadOnlyCollection<T> auf:

// Riok.Mapperly 4.3.1 generated output, trimmed
private List<OrderLineDto> MapToListOfOrderLineDto(IReadOnlyCollection<OrderLine> source)
{
    var target = new List<OrderLineDto>(source.Count);
    foreach (var item in source)
        target.Add(MapToOrderLineDto(item));
    return target;
}

Das Durchlaufen einer List<T> über ein Interface boxt deren Struct-Enumerator: 40 Byte pro Bestellung, 40.000 Byte über den gesamten Stapel. Deklarieren Sie den Mapper für die verschachtelte Sammlung selbst mit einem konkreten List<OrderLine>-Parameter, verschwindet das. Genau solche Dinge lassen sich finden und beheben, weil der generierte Code auf der Festplatte liegt, und das ist der praktische Unterschied zwischen einem Source Generator und einem kompilierten Expression Tree.

Der Punkt, der die Entscheidung abnimmt: Native AOT

Veröffentlichen Sie eine Konsolenanwendung, die AutoMapper 16.2.0 aufruft, mit <PublishAot>true</PublishAot> unter net10.0, und der Build warnt:

AutoMapper.dll : warning IL2104: Assembly 'AutoMapper' produced trim warnings.
AutoMapper.dll : warning IL3053: Assembly 'AutoMapper' produced AOT analysis warnings.

Warnungen lassen sich leicht ignorieren. Die entstehende Binärdatei nicht:

Unhandled exception. System.TypeInitializationException: A type initializer threw an exception.
 ---> System.ArgumentNullException: Value cannot be null. (Parameter 'method')
   at System.Linq.Expressions.Expression.Call(MethodInfo, Expression)
   at AutoMapper.Execution.ExpressionBuilder..cctor()
   at AutoMapper.MapperConfiguration..ctor(MapperConfigurationExpression, ILoggerFactory)

Der Trimmer hat eine Methode entfernt, die ExpressionBuilder per Reflection sucht, deshalb stirbt der statische Konstruktor vor Ihrem ersten Mapping. Die entsprechende Mapperly-Anwendung mit denselben Einstellungen erzeugt null IL-Warnungen, liefert eine native Binärdatei von 1.1 MB und läuft. Das ist kein Feinschliffproblem, das sich mit DynamicDependency-Attributen an der Aufrufstelle lösen lässt; es ist eine Eigenschaft davon, Maps zur Laufzeit aus Expression Trees zu bauen, also dieselbe Falle wie in was ist trim-sicherer Code und wie schreibe ich ihn beschrieben. Wenn Native AOT auf Ihrer Roadmap steht, ist die Entscheidung bereits gefallen.

Die mildere Variante desselben Effekts ist der Kaltstart. Die Konfiguration aufzubauen und das erste Mapping für drei Typen auszuführen, dauerte auf dieser Maschine 33 Millisekunden, gegenüber 1 Millisekunde für new OrderMapper() plus dessen ersten Aufruf. In einer langlebigen Webanwendung ist das unsichtbar. In einer Lambda ist es ein messbarer Anteil eines kalten Aufrufs, weshalb es in die Kaltstartzeit einer .NET-Lambda auf AWS reduzieren auftaucht.

Wo der Sicherheitsunterschied tatsächlich sichtbar wird

Fügen Sie einem DTO eine Slug-Eigenschaft hinzu und vergessen Sie, sie zu mappen. AutoMapper 16.2.0 mappt das Objekt trotzdem:

map ok: Id=1 Name=n Slug=''

AssertConfigurationIsValid() findet es zwar und wirft AutoMapperConfigurationException mit “Unmapped members were found”, aber nur wenn Sie daran gedacht haben, es aufzurufen, und nur für nicht gemappte Ziel-Member. Eine Quell-Eigenschaft, die kein DTO mehr erreicht, wird überhaupt nicht gemeldet.

Mapperly meldet beide Richtungen zur Compile-Zeit, mit dem tatsächlichen Meldungstext:

warning RMG020: The member InternalNote on the mapping source type Diag.Source
                is not mapped to any member on the mapping target type Diag.Target
warning RMG012: The member Slug on the mapping target type Diag.Target
                was not found on the mapping source type Diag.Source

Standardmäßig sind das Warnungen, die in einem lauten Build untergehen. Eskalieren Sie sie in der .editorconfig, und der Build scheitert vollständig:

[*.cs]
dotnet_diagnostic.RMG012.severity = error
dotnet_diagnostic.RMG020.severity = error

Diese Einstellung macht aus Mapperly statt “einem schnelleren AutoMapper” eine andere Werkzeugkategorie: Mapping-Fehler sind keine Produktionsvorfälle mehr, sondern Build-Fehler. Sie ist zugleich die klarste Illustration dafür, warum Source Generators die Build-Abhängigkeit wert sind.

Handgeschriebenes Mapping bietet, der Vollständigkeit halber, keine solche Prüfung. Eine vergessene Zuweisung in einer ToDto-Methode ist genauso still wie bei AutoMapper. Ihre Sicherheit kommt aus der Sichtbarkeit im Code-Review, nicht aus Werkzeugen.

Die Entscheidung

Nehmen Sie für neuen Code standardmäßig Mapperly und eskalieren Sie RMG012 und RMG020 vom ersten Tag an zu Fehlern, damit der Nutzen wirklich eintritt. Schreiben Sie von Hand, wenn das Projekt klein oder die Strukturen unregelmäßig sind, und akzeptieren Sie, dass Sie Werkzeugprüfungen gegen Prüfbarkeit tauschen. Bleiben Sie bei AutoMapper, wenn eine reife, ProjectTo-lastige Codebasis bereits funktioniert, Sie unter der Community-Schwelle liegen und Native AOT nicht auf der Roadmap steht; sobald eine dieser drei Bedingungen entfällt, starten Sie die Migration, statt die Lizenz einzuplanen. Die Performance-Tabelle ist der uninteressanteste Teil dieses Vergleichs. Trim-Sicherheit und Diagnosen zur Compile-Zeit sind das, was das Verhalten einer Codebasis wirklich verändert.

Verwandte Artikel

Quellen

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Zurück