AutoMapper不会使用Mapper.ProjectTo映射嵌套对象。

huangapple go评论82阅读模式
英文:

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&lt;Location, LocationResponse&gt;()
                .ForMember(dest =&gt; dest.Address, opt =&gt; opt.DoNotAllowNull())

This changes the Expression that AutoMapper generates to this

[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression]
    .AsNoTracking()
    .Where(x =&gt; !x.IsDeleted &amp;&amp; (x.BusinessId == AuthService.GetBusinessIdFromRequestDomain()))
    .Select(
        dtoLocation =&gt; 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
                }
        })

huangapple
  • 本文由 发表于 2023年7月10日 22:45:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76654878.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定