Open Closed

AutoMapper to Mapperly migration causes widespread NullReferenceExceptions due to strict null-handling #10355


User avatar
0
pareeks@gccontractor.org created
  • Exception message and full stack trace: System.NullReferenceException: 'Object reference not set to an instance of an object.' source was null.
  • Steps to reproduce the issue: I recently upgraded an existing ABP.io project from .NET 9 to .NET 10. As part of the upgrade, ABP is moving from AutoMapper to Mapperly. After migrating an ABP application from AutoMapper to Mapperly, we are encountering numerous NullReferenceExceptions across the application during DTO mapping. The root cause is that AutoMapper implicitly handled null source navigation properties by skipping mapping or creating default values, whereas Mapperly is strict and assumes non-nullable mappings unless nullability is explicitly modeled.
  • Expected behavior A migration path similar to AutoMapper, where:
  • null source navigation properties are handled gracefully
  • Mapping either skips assignment or assigns default values
  • No runtime NullReferenceException occurs by default
  • Actual behavior
  • Mapperly generates mapping code that assumes non-nullable source objects
  • NullReferenceException is thrown when optional navigations are null
  • No global configuration option exists to preserve AutoMapper-style default handling
  • Question / Request Is there any global or framework-level configuration in ABP / Mapperly that allows:
  • Automatic handling of null source navigation properties
  • Default-value assignment or safe skipping (similar to AutoMapper)
  • Reducing the need to update hundreds of DTOs with explicit nullable annotations (?) If not, is there a recommended migration strategy from AutoMapper to Mapperly for large existing ABP applications that relied on AutoMapper’s implicit null-handling behavior?

2 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    1. Solution

    There is currently no ABP‑specific or Mapperly‑level “global switch” that makes Mapperly behave like AutoMapper regarding null source / navigation properties. Mapperly’s behavior is intentionally strict and driven by C# nullability. So:

    • You cannot globally say “ignore null source members and don’t throw NREs” like you could effectively get with AutoMapper’s implicit behavior.
    • You must model nullability correctly and/or write explicit null‑safe mappings where needed.

    Given that, for a large ABP application the realistic options are:


    A. Short‑Term: Keep AutoMapper for the parts that depend on its null behavior

    ABP v10 supports using AutoMapper and Mapperly side by side. For a big system that heavily relied on AutoMapper’s implicit null handling, the most practical migration path is:

    1. Keep AutoMapper enabled for existing mappings
      • Keep (or re‑add) the AutoMapper integration in the projects where you can’t yet afford to refactor all mappings:
    [DependsOn(
             typeof(AbpAutoMapperModule),
             // other modules...
         )]
         public class MyProjectApplicationModule : AbpModule
         {
             public override void ConfigureServices(ServiceConfigurationContext context)
             {
                 context.Services.AddAutoMapperObjectMapper<MyProjectApplicationModule>();
    
                 Configure<AbpAutoMapperOptions>(options =>
                 {
                     options.AddMaps<MyProjectApplicationModule>(validate: true);
                 });
             }
         }
    
    • Continue to use your existing Profile classes and CreateMap<,>() configurations. AutoMapper will keep handling nulls the way it always has.
    1. Introduce Mapperly gradually for new / refactored areas
      • For new modules or new DTOs, configure Mapperly:
    [DependsOn(
             typeof(AbpMapperlyModule),
             // ...
         )]
         public class MyNewModule : AbpModule
         {
             public override void ConfigureServices(ServiceConfigurationContext context)
             {
                 context.Services.AddMapperlyObjectMapper<MyNewModule>();
             }
         }
    
    • Use Mapperly mappers only where you explicitly design nullability correctly and can tolerate its strictness.
    1. Use ObjectMapperContext to select which mapper is used
      • In an app service that should still use AutoMapper:
    public class LegacyAppService : ApplicationService
         {
             public LegacyAppService()
             {
                 ObjectMapperContext = typeof(MyProjectApplicationModule); // AutoMapper context
             }
         }
    
    • In a new app service that should use Mapperly:
    public class NewAppService : ApplicationService
         {
             public NewAppService()
             {
                 ObjectMapperContext = typeof(MyNewModule); // Mapperly context
             }
         }
    

    This allows you to migrate incrementally instead of forcing the whole codebase to Mapperly’s strict rules at once.


    B. Long‑Term: Migrate to Mapperly with explicit nullability and null‑safe mappings

    For the parts you do want to move fully to Mapperly, the strategy is:

    1. Make nullability explicit in your models and DTOs

    Mapperly trusts your C# nullability annotations:

    • If a navigation can be missing:
    public class Order
      {
          public Customer? Customer { get; set; }  // nullable if optional
      }
    
      public class OrderDto
      {
          public string? CustomerName { get; set; }  // nullable if Customer may be null
      }
    
    • Avoid declaring as non‑nullable when it can actually be null (that’s what leads to NREs in the generated code).

    Run with nullable reference types enabled and clean up warnings on your mapping types; this drastically reduces Mapperly‑induced NREs.

    2. Use custom logic for nested null‑sensitive properties

    Where you previously relied on AutoMapper’s “it just works” behavior for nested paths, switch to null‑safe custom logic instead of direct property flattening.

    Example:

    [Mapper]
    public partial class OrderMapper : MapperBase<Order, OrderDto>
    {
        public override partial OrderDto Map(Order source);
        public override partial void Map(Order source, OrderDto destination);
    
        // Custom null-safe logic
        partial void Map(Order source, OrderDto destination)
        {
            destination.CustomerName = source.Customer?.Name ?? string.Empty;
        }
    }
    

    or use an AfterMap pattern in a partial method—key idea is: do the ?. and ?? yourself rather than relying on Mapperly to silently ignore nulls.

    3. Align ABP Mapperly configuration

    Ensure your module is correctly configured for Mapperly so that ObjectMapper.Map<,>() goes through Mapperly for that context:

    using Volo.Abp.Mapperly;
    
    [DependsOn(typeof(AbpMapperlyModule))]
    public class MyMapperlyApplicationModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            // default Mapperly object mapper
            context.Services.AddMapperlyObjectMapper();
    
            // optional: contextual Mapperly mapper for this module
            context.Services.AddMapperlyObjectMapper<MyMapperlyApplicationModule>();
        }
    }
    

    Then, in your service:

    public class MyAppService : ApplicationService
    {
        public MyAppService()
        {
            ObjectMapperContext = typeof(MyMapperlyApplicationModule);
        }
    
        public Task<OrderDto> GetAsync(Order order)
        {
            var dto = ObjectMapper.Map<Order, OrderDto>(order); // Mapperly-based
            return Task.FromResult(dto);
        }
    }
    

    4. Systematically update problematic mappings

    For the crashes you’re seeing now:

    • Find the mappings that flatten navigations (e.g. Customer.Name -> CustomerName, Parent.Child.GrandChild.Prop, etc.).
    • For each:
      • Make source navigations nullable where they can actually be null.
      • Make target properties nullable or give them a default (e.g. string.Empty) through custom code.
      • Replace AutoMapper‑style convention mapping with explicit null‑safe logic in Mapperly.

    This is mechanical but can be scripted/assisted by IDE analyzers focusing on:

    • Non‑nullable DTO properties sourced from potentially nullable navigations.
    • Places where EF navigation is optional but declared non‑nullable.

    C. Summary of what is and is not possible

    • Does ABP/Mapperly provide a global option to mimic AutoMapper’s null behavior?
      No. There is no framework‑level or Mapperly‑level setting that will:

      • Automatically skip assignments where the source navigation is null, or
      • Automatically assign default values for such cases.
    • Recommended migration strategy for large apps:

      1. Short‑term:
        • Keep AutoMapper around for existing mappings that count on its null semantics.
        • Introduce Mapperly only in new or refactored parts.
      2. Long‑term:
        • Gradually migrate modules to Mapperly by:
          • Fixing nullability in entities/DTOs.
          • Writing explicit null‑safe mapping logic for nested/navigational members.
          • Configuring ObjectMapperContext and AbpMapperlyModule properly per module.

    1. Sources
    • ABP migration guide – AutoMapper → Mapperly (concepts, coexistence, registration):
      https://abp.io/docs/10.0/release-info/migration-guides/AutoMapper-To-Mapperly

    • ABP object‑to‑object mapping & Mapperly integration (MapperBase, configuration, ObjectMapper):
      https://abp.io/docs/latest/framework/infrastructure/object-to-object-mapping

    • Mapperly mapper configuration & nullability (general behavior; no global ignore‑null flag):
      https://mapperly.riok.app/docs/configuration/mapper/
      https://mapperly.riok.app/docs/configuration/flattening/


    This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.

  • User Avatar
    0
    pareeks@gccontractor.org created

    I’m looking for a way to handle this either via a global Mapperly configuration or a mapper-level configuration, without having to make widespread nullable changes across the domain and DTO models.

    For context, here’s a simple example:

    public partial class HrCountryWithNavigationPropertiesToDtoMapper 
        : MapperBase<HrCountryWithNavigationProperties, HrCountryWithNavigationPropertiesDto>
    {
        public override partial HrCountryWithNavigationPropertiesDto Map(HrCountryWithNavigationProperties source);
        public override partial void Map(HrCountryWithNavigationProperties source, HrCountryWithNavigationPropertiesDto destination);
    }
    
    public class HrCountryWithNavigationProperties
    {
        public HrCountry HrCountry { get; set; }
        public HrMailCountry HrMailCountry { get; set; }
    }
    
    public class HrCountryWithNavigationPropertiesDto
    {
        public HrCountryDto HrCountry { get; set; }
        public HrMailCountryDto HrMailCountry { get; set; }
    }
    

    During runtime, if HrMailCountry (or any navigation property) is null, Mapperly throws a NullReferenceException. This differs from AutoMapper’s behavior, where null navigation properties were implicitly ignored or mapped to default values. Making these properties nullable (HrMailCountry?, HrMailCountryDto?) resolves the issue, but that would require extensive refactoring across the solution, which I’d like to avoid if possible.

Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.2.0-preview. Updated on January 22, 2026, 11:30
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.