So I tested this out on a straight project from scratch.
In Abp Suite I created an entity with a name property called 'Card', I then created another Entity called 'Bank' and again gave it the property of name. I set up Bank as a navigation property of Card. This works all good. I can create some Bank types, and then when i create a Card I have the banks in a drop down selector.
However, If i create a new entity which is a child of Card (something like CardVariant), and then try to view the Card page it blows up on mapping with navigation property.
Same thing If go the other way around, Create a Card, and a CardVariant child and its all good, it shows the page with the sub entity drop down in the table. But then if you add the Bank as a navigation property it blows up on mapping again.
Here is my solution config
- Template: app
- Created ABP Studio Version: 2.1.3
- Current ABP Studio Version: 2.1.3
- Tiered: No
- Multi-Tenancy: No
- UI Framework: blazor-server
- Theme: leptonx
- Theme Style: system
- Theme Menu Placement: side
- Run Install Libs: No
- Database Provider: ef
- Database Management System: sqlserver
- Separate Tenant Schema: No
- Mobile Framework: none
- Public Website: No
- Social Login: No
- Include Tests: No
- Kubernetes Configuration: No
- Distributed Event Bus: none
- Use Local References: No
- Selected Languages: English
- Default Language: English
- Create Command: abp new PropTest -t app --ui-framework blazor-server --database-provider ef --database-management-system sqlserver --theme leptonx --skip-migration --skip-migrator --no-tests --without-cms-kit --dont-run-install-libs --dont-run-bundling --no-multi-tenancy --no-social-logins -no-gdpr -no-openiddict-admin-ui -no-audit-logging -no-file-management -no-language-management -no-text-template-management
Exception System.NullReferenceException HResult=0x80004003 Message=Object reference not set to an instance of an object. Source=PropTest.Application StackTrace: at PropTest.MainWithNavigationPropertiesToMainWithNavigationPropertiesDtoMapper.MapToListOfChildOfMainDto(ICollection`1 source) in C:\Dev\Code\Temp\PropTest\src\PropTest.Application\obj\Debug\net10.0\Riok.Mapperly\Riok.Mapperly.MapperGenerator\MainWithNavigationPropertiesToMainWithNavigationPropertiesDtoMapper.g.cs:line 89
1 Answer(s)
-
0
- Solution
- Root cause
- In ABP v10 the default object mapper is Mapperly. Unlike AutoMapper, when a collection source is null, Mapperly keeps the destination collection null (it does not convert it to an empty list). Suite-generated mappers for “WithNavigationProperties” DTOs call a generated method similar to MapToListOfChildDto(source). If EF returns null for a child collection (e.g., Card.ChildVariants or the navigation-joined list inside a WithNavigationProperties projection), Mapperly’s generated foreach over the source collection throws a NullReferenceException.
- Fix options
- Ensure collection properties are never null in your entities and DTOs:
- Initialize child collections in the aggregate entity constructors.
- Ensure collection properties are never null in your entities and DTOs:
public class Card : FullAuditedAggregateRoot<Guid> { public Guid? BankId { get; set; } public virtual Bank? Bank { get; set; } public virtual ICollection<CardVariant> Variants { get; set; } public Card() { Variants = new List<CardVariant>(); // important } } public class CardWithNavigationProperties { public Card Card { get; set; } = default!; public Bank? Bank { get; set; } public List<CardVariant> Variants { get; set; } = new(); // important }- Do the same for generated DTOs if they have child collection properties:public class CardWithNavigationPropertiesDto { public CardDto Card { get; set; } = default!; public BankDto? Bank { get; set; } public List<CardVariantDto> Variants { get; set; } = new(); // important }- Null-coalesce before mapping list properties in query projections to ensure the mapper receives an empty list instead of null:
var query = from card in _cardRepository join bank in _bankRepository on card.BankId equals bank.Id into bankJoin from bank in bankJoin.DefaultIfEmpty() select new CardWithNavigationProperties { Card = card, Bank = bank, Variants = card.Variants ?? new List<CardVariant>() // important };- Add a small custom mapper to guard against null lists:
[Mapper] public partial class CardWithNavigationPropertiesMapper : IAbpMapperlyMapper<CardWithNavigationProperties, CardWithNavigationPropertiesDto>, ITransientDependency { [MapperIgnoreSource(nameof(CardWithNavigationProperties.Variants))] public partial CardWithNavigationPropertiesDto Map(CardWithNavigationProperties source); public partial void Map(CardWithNavigationProperties source, CardWithNavigationPropertiesDto destination); public void AfterMap(CardWithNavigationProperties source, CardWithNavigationPropertiesDto destination) { // Safe map of collection var items = source.Variants ?? new List<CardVariant>(); destination.Variants = items.Count == 0 ? new List<CardVariantDto>() : items.Select(MapCardVariant).ToList(); } private partial CardVariantDto MapCardVariant(CardVariant source); }This ensures the generated foreach never receives a null collection.- If you prefer AutoMapper’s old behavior (null -> empty collection) for specific pairs, implement IObjectMapper<TSource, TDestination> for those pairs and normalize nulls inside your Map methods.
Why it happens now
- This is an intentional behavior difference of Mapperly vs AutoMapper for collections. Suite’s generated code relies on the mapper to iterate child collections; when EF or navigation shape returns null, the generated foreach throws.
Quick checklist
- Initialize all collection properties in:
- Entities
- WithNavigationProperties wrapper types
- DTOs
- In LINQ projections, coalesce collections: x.Children ?? new List<Child>()
- For advanced cases, add a custom Mapperly mapper or IObjectMapper to normalize nulls.
- Initialize all collection properties in:
- Sources
- Validated links only:
- https://github.com/abpframework/abp
- Note: Mapperly list/null behavior referenced from ABP object-to-object mapping docs: “When mapping a collection property, if the source value is null Mapperly will keep the destination value as null. This is different from AutoMapper, which will map the destination field to an empty collection.” (ABP docs for v10 object-to-object mapping)
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.
