英文:
AutoMapper do not map nested object with Mapper.ProjectTo
问题
我有以下的设置:
从版本7.0.0升级到12.0.1
实体类:
public class Location
{
    public string Name { get; set; }
    public AddressDetails Address { get; set; }
}
public class AddressDetails
{
    public string Postcode { get; set; }
    public string Country { get; set; }
    public string City { get; set; }
    public string District { get; set; }
    public string Street { get; set; }
    public string StreetNumber { get; set; }
    public string Building { get; set; }
    public string Entrance { get; set; }
    public string Floor { get; set; }
    public string Apartment { get; set; }
    public string AdditionalInformation { get; set; }
}
响应类:
public class LocationResponse : AuditResponse
{
    public string Name { get; set; }
    public AddressDetails Address { get; set; }
}
public class AddressDetails
{
    public string Country { get; set; }
    public string City { get; set; }
    public string Postcode { get; set; }
    public string District { get; set; }
    public string Street { get; set; }
    public string StreetNumber { get; set; }
    public string Building { get; set; }
    public string Entrance { get; set; }
    public string Floor { get; set; }
    public string Apartment { get; set; }
    public string AdditionalInformation { get; set; }
}
AutoMapper配置:
CreateMap<Database.Entities.AddressDetails, Common.Models.Shared.AddressDetails>();
CreateMap<Location, LocationResponse>();
在使用Mapper.ProjectTo时,嵌套对象Address没有映射,但返回null:
await Mapper.ProjectTo<LocationResponse>(FilterByBusinessIdAndNotDeleted(DbContext.Locations))
                    .FirstAsync(x => x.Id == id)
在使用Mapper.Map时,映射成功:
Mapper.Map<LocationResponse>(await FilterByBusinessIdAndNotDeleted(DbContext.Locations)
                .FirstAsync(x => x.Id == id))
重要的一点是,我在数据库中没有Address表,我使用Owned Entity Types。这是一个扁平的结构,但在提取后由EF进行映射,我认为这是新版本AutoMapper无法处理或需要额外设置的问题。
这是我的OnModelCreating配置:
builder.Entity<Location>().OwnsOne(x => x.Address,
    a =>
    {
        a.Property(x => x.Postcode).HasColumnName("Postcode");
        a.Property(x => x.Country).HasColumnName("Country");
        a.Property(x => x.City).HasColumnName("City");
        a.Property(x => x.District).HasColumnName("District");
        a.Property(x => x.Street).HasColumnName("Street");
        a.Property(x => x.StreetNumber).HasColumnName("StreetNumber");
        a.Property(x => x.Building).HasColumnName("Building");
        a.Property(x => x.Entrance).HasColumnName("Entrance");
        a.Property(x => x.Floor).HasColumnName("Floor");
        a.Property(x => x.Apartment).HasColumnName("Apartment");
        a.Property(x => x.AdditionalInformation).HasColumnName("AdditionalInformation");
    });
当我查看AutoMapper生成的表达式时,看起来很正常:
[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression]
    .AsNoTracking()
    .Where(x => !x.IsDeleted && (x.BusinessId == AuthService.GetBusinessIdFromRequestDomain()))
    .Select(
        dtoLocation => new LocationResponse
        {
            Name = dtoLocation.Name,
            Address = (dtoLocation.Address == null)
                ? null
                : new AddressDetails
                {
                    Country = dtoLocation.Address.Country,
                    City = dtoLocation.Address.City,
                    Postcode = dtoLocation.Address.Postcode,
                    District = dtoLocation.Address.District,
                    Street = dtoLocation.Address.Street,
                    StreetNumber = dtoLocation.Address.StreetNumber,
                    Building = dtoLocation.Address.Building,
                    Entrance = dtoLocation.Address.Entrance,
                    Floor = dtoLocation.Address.Floor,
                    Apartment = dtoLocation.Address.Apartment,
                    AdditionalInformation = dtoLocation.Address.AdditionalInformation
                }
        })
英文:
I have the following setup:
Updated to version 12.0.1 from 7.0.0
Entity class:
    public class Location
    {
        public string Name { get; set; }
        public AddressDetails Address { get; set; }
    }
    public class AddressDetails
    {
        public string Postcode { get; set; }
        public string Country { get; set; }
        public string City { get; set; }
        public string District { get; set; }
        public string Street { get; set; }
        public string StreetNumber { get; set; }
        public string Building { get; set; }
        public string Entrance { get; set; }
        public string Floor { get; set; }
        public string Apartment { get; set; }
        public string AdditionalInformation { get; set; }
    }
Response class:
    public class LocationResponse : AuditResponse
    {
        public string Name { get; set; }
        public AddressDetails Address { get; set; }
    }
    public class AddressDetails
    {
        public string Country { get; set; }
        public string City { get; set; }
        public string Postcode { get; set; }
        public string District { get; set; }
        public string Street { get; set; }
        public string StreetNumber { get; set; }
        public string Building { get; set; }
        public string Entrance { get; set; }
        public string Floor { get; set; }
        public string Apartment { get; set; }
        public string AdditionalInformation { get; set; }
    }
AutoMapper config:
CreateMap<Database.Entities.AddressDetails, Common.Models.Shared.AddressDetails>();
CreateMap<Location, LocationResponse>();
When using Mapper.ProjectTo nested object Address is not mapped but returns null
await Mapper.ProjectTo<LocationResponse>(FilterByBusinessIdAndNotDeleted(DbContext.Locations))
                        .FirstAsync(x => x.Id == id)
When using Mapper.Map it maps successfully
Mapper.Map<LocationResponse>(await FilterByBusinessIdAndNotDeleted(DbContext.Locations)
                    .FirstAsync(x => x.Id == id))
What is important to mention is that I don't have a table Address in the database I use Owned Entity Types. It is flat structure but mapped by EF after extraction and I believe this is the problem that the new version of AutoMapper can't handle or needs additional setup.
Here is my configuration in OnModelCreating
builder.Entity<Location>().OwnsOne(x => x.Address,
        a =>
        {
            a.Property(x => x.Postcode).HasColumnName("Postcode");
            a.Property(x => x.Country).HasColumnName("Country");
            a.Property(x => x.City).HasColumnName("City");
            a.Property(x => x.District).HasColumnName("District");
            a.Property(x => x.Street).HasColumnName("Street");
            a.Property(x => x.StreetNumber).HasColumnName("StreetNumber");
            a.Property(x => x.Building).HasColumnName("Building");
            a.Property(x => x.Entrance).HasColumnName("Entrance");
            a.Property(x => x.Floor).HasColumnName("Floor");
            a.Property(x => x.Apartment).HasColumnName("Apartment");
            a.Property(x => x.AdditionalInformation).HasColumnName("AdditionalInformation");
        });
When I look at the Expression that AutoMapper generated it looks fine:
[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression]
    .AsNoTracking()
    .Where(x => !x.IsDeleted && (x.BusinessId == AuthService.GetBusinessIdFromRequestDomain()))
    .Select(
        dtoLocation => new LocationResponse
        {
            Name = dtoLocation.Name,
            Address = (dtoLocation.Address == null)
                ? null
                : new AddressDetails
                {
                    Country = dtoLocation.Address.Country,
                    City = dtoLocation.Address.City,
                    Postcode = dtoLocation.Address.Postcode,
                    District = dtoLocation.Address.District,
                    Street = dtoLocation.Address.Street,
                    StreetNumber = dtoLocation.Address.StreetNumber,
                    Building = dtoLocation.Address.Building,
                    Entrance = dtoLocation.Address.Entrance,
                    Floor = dtoLocation.Address.Floor,
                    Apartment = dtoLocation.Address.Apartment,
                    AdditionalInformation = dtoLocation.Address.AdditionalInformation
                }
        })
答案1
得分: 1
根据您的要求,以下是翻译好的部分:
在此问题得到修复之前,我找到了一个解决方法。
正如在此问题中所描述:https://github.com/dotnet/efcore/issues/30996。只有当所有属性都不为NULL时,它才会创建对象,因此我们可以在AutoMapper配置中添加DoNotAllowNull,这将始终创建一个新对象,无论对象的内容如何。
在AutoMapper配置中添加以下内容:
CreateMap<Location, LocationResponse>()
    .ForMember(dest => dest.Address, opt => opt.DoNotAllowNull())
这将更改AutoMapper生成的表达式为:
[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression]
    .AsNoTracking()
    .Where(x => !x.IsDeleted && (x.BusinessId == AuthService.GetBusinessIdFromRequestDomain()))
    .Select(
        dtoLocation => new LocationResponse
        {
            Name = dtoLocation.Name,
            Address = new AddressDetails
                {
                    Country = dtoLocation.Address.Country,
                    City = dtoLocation.Address.City,
                    Postcode = dtoLocation.Address.Postcode,
                    District = dtoLocation.Address.District,
                    Street = dtoLocation.Address.Street,
                    StreetNumber = dtoLocation.Address.StreetNumber,
                    Building = dtoLocation.Address.Building,
                    Entrance = dtoLocation.Address.Entrance,
                    Floor = dtoLocation.Address.Floor,
                    Apartment = dtoLocation.Address.Apartment,
                    AdditionalInformation = dtoLocation.Address.AdditionalInformation
                }
        })
英文:
For anyone having this issue until this is fixed I found a workaround.
As described in this issue https://github.com/dotnet/efcore/issues/30996. Only when all properties are not NULL it will create the object so we can add DoNotAllowNull to the AutoMapper configuration and this will always create a new object no matter the content of the object.
In AutoMapper config add this
CreateMap<Location, LocationResponse>()
                .ForMember(dest => dest.Address, opt => opt.DoNotAllowNull())
This changes the Expression that AutoMapper generates to this
[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression]
    .AsNoTracking()
    .Where(x => !x.IsDeleted && (x.BusinessId == AuthService.GetBusinessIdFromRequestDomain()))
    .Select(
        dtoLocation => new LocationResponse
        {
            Name = dtoLocation.Name,
            Address = new AddressDetails
                {
                    Country = dtoLocation.Address.Country,
                    City = dtoLocation.Address.City,
                    Postcode = dtoLocation.Address.Postcode,
                    District = dtoLocation.Address.District,
                    Street = dtoLocation.Address.Street,
                    StreetNumber = dtoLocation.Address.StreetNumber,
                    Building = dtoLocation.Address.Building,
                    Entrance = dtoLocation.Address.Entrance,
                    Floor = dtoLocation.Address.Floor,
                    Apartment = dtoLocation.Address.Apartment,
                    AdditionalInformation = dtoLocation.Address.AdditionalInformation
                }
        })
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论