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

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

AutoMapper do not map nested object with Mapper.ProjectTo

问题

我有以下的设置:

从版本7.0.0升级到12.0.1

实体类:

  1. public class Location
  2. {
  3. public string Name { get; set; }
  4. public AddressDetails Address { get; set; }
  5. }
  6. public class AddressDetails
  7. {
  8. public string Postcode { get; set; }
  9. public string Country { get; set; }
  10. public string City { get; set; }
  11. public string District { get; set; }
  12. public string Street { get; set; }
  13. public string StreetNumber { get; set; }
  14. public string Building { get; set; }
  15. public string Entrance { get; set; }
  16. public string Floor { get; set; }
  17. public string Apartment { get; set; }
  18. public string AdditionalInformation { get; set; }
  19. }

响应类:

  1. public class LocationResponse : AuditResponse
  2. {
  3. public string Name { get; set; }
  4. public AddressDetails Address { get; set; }
  5. }
  6. public class AddressDetails
  7. {
  8. public string Country { get; set; }
  9. public string City { get; set; }
  10. public string Postcode { get; set; }
  11. public string District { get; set; }
  12. public string Street { get; set; }
  13. public string StreetNumber { get; set; }
  14. public string Building { get; set; }
  15. public string Entrance { get; set; }
  16. public string Floor { get; set; }
  17. public string Apartment { get; set; }
  18. public string AdditionalInformation { get; set; }
  19. }

AutoMapper配置:

  1. CreateMap<Database.Entities.AddressDetails, Common.Models.Shared.AddressDetails>();
  2. CreateMap<Location, LocationResponse>();

在使用Mapper.ProjectTo时,嵌套对象Address没有映射,但返回null:

  1. await Mapper.ProjectTo<LocationResponse>(FilterByBusinessIdAndNotDeleted(DbContext.Locations))
  2. .FirstAsync(x => x.Id == id)

在使用Mapper.Map时,映射成功:

  1. Mapper.Map<LocationResponse>(await FilterByBusinessIdAndNotDeleted(DbContext.Locations)
  2. .FirstAsync(x => x.Id == id))

重要的一点是,我在数据库中没有Address表,我使用Owned Entity Types。这是一个扁平的结构,但在提取后由EF进行映射,我认为这是新版本AutoMapper无法处理或需要额外设置的问题。

这是我的OnModelCreating配置:

  1. builder.Entity<Location>().OwnsOne(x => x.Address,
  2. a =>
  3. {
  4. a.Property(x => x.Postcode).HasColumnName("Postcode");
  5. a.Property(x => x.Country).HasColumnName("Country");
  6. a.Property(x => x.City).HasColumnName("City");
  7. a.Property(x => x.District).HasColumnName("District");
  8. a.Property(x => x.Street).HasColumnName("Street");
  9. a.Property(x => x.StreetNumber).HasColumnName("StreetNumber");
  10. a.Property(x => x.Building).HasColumnName("Building");
  11. a.Property(x => x.Entrance).HasColumnName("Entrance");
  12. a.Property(x => x.Floor).HasColumnName("Floor");
  13. a.Property(x => x.Apartment).HasColumnName("Apartment");
  14. a.Property(x => x.AdditionalInformation).HasColumnName("AdditionalInformation");
  15. });

当我查看AutoMapper生成的表达式时,看起来很正常:

  1. [Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression]
  2. .AsNoTracking()
  3. .Where(x => !x.IsDeleted && (x.BusinessId == AuthService.GetBusinessIdFromRequestDomain()))
  4. .Select(
  5. dtoLocation => new LocationResponse
  6. {
  7. Name = dtoLocation.Name,
  8. Address = (dtoLocation.Address == null)
  9. ? null
  10. : new AddressDetails
  11. {
  12. Country = dtoLocation.Address.Country,
  13. City = dtoLocation.Address.City,
  14. Postcode = dtoLocation.Address.Postcode,
  15. District = dtoLocation.Address.District,
  16. Street = dtoLocation.Address.Street,
  17. StreetNumber = dtoLocation.Address.StreetNumber,
  18. Building = dtoLocation.Address.Building,
  19. Entrance = dtoLocation.Address.Entrance,
  20. Floor = dtoLocation.Address.Floor,
  21. Apartment = dtoLocation.Address.Apartment,
  22. AdditionalInformation = dtoLocation.Address.AdditionalInformation
  23. }
  24. })
英文:

I have the following setup:

Updated to version 12.0.1 from 7.0.0

Entity class:

  1. public class Location
  2. {
  3. public string Name { get; set; }
  4. public AddressDetails Address { get; set; }
  5. }
  6. public class AddressDetails
  7. {
  8. public string Postcode { get; set; }
  9. public string Country { get; set; }
  10. public string City { get; set; }
  11. public string District { get; set; }
  12. public string Street { get; set; }
  13. public string StreetNumber { get; set; }
  14. public string Building { get; set; }
  15. public string Entrance { get; set; }
  16. public string Floor { get; set; }
  17. public string Apartment { get; set; }
  18. public string AdditionalInformation { get; set; }
  19. }

Response class:

  1. public class LocationResponse : AuditResponse
  2. {
  3. public string Name { get; set; }
  4. public AddressDetails Address { get; set; }
  5. }
  6. public class AddressDetails
  7. {
  8. public string Country { get; set; }
  9. public string City { get; set; }
  10. public string Postcode { get; set; }
  11. public string District { get; set; }
  12. public string Street { get; set; }
  13. public string StreetNumber { get; set; }
  14. public string Building { get; set; }
  15. public string Entrance { get; set; }
  16. public string Floor { get; set; }
  17. public string Apartment { get; set; }
  18. public string AdditionalInformation { get; set; }
  19. }

AutoMapper config:

  1. CreateMap<Database.Entities.AddressDetails, Common.Models.Shared.AddressDetails>();
  2. CreateMap<Location, LocationResponse>();

When using Mapper.ProjectTo nested object Address is not mapped but returns null

  1. await Mapper.ProjectTo<LocationResponse>(FilterByBusinessIdAndNotDeleted(DbContext.Locations))
  2. .FirstAsync(x => x.Id == id)

When using Mapper.Map it maps successfully

  1. Mapper.Map<LocationResponse>(await FilterByBusinessIdAndNotDeleted(DbContext.Locations)
  2. .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

  1. builder.Entity<Location>().OwnsOne(x => x.Address,
  2. a =>
  3. {
  4. a.Property(x => x.Postcode).HasColumnName("Postcode");
  5. a.Property(x => x.Country).HasColumnName("Country");
  6. a.Property(x => x.City).HasColumnName("City");
  7. a.Property(x => x.District).HasColumnName("District");
  8. a.Property(x => x.Street).HasColumnName("Street");
  9. a.Property(x => x.StreetNumber).HasColumnName("StreetNumber");
  10. a.Property(x => x.Building).HasColumnName("Building");
  11. a.Property(x => x.Entrance).HasColumnName("Entrance");
  12. a.Property(x => x.Floor).HasColumnName("Floor");
  13. a.Property(x => x.Apartment).HasColumnName("Apartment");
  14. a.Property(x => x.AdditionalInformation).HasColumnName("AdditionalInformation");
  15. });

When I look at the Expression that AutoMapper generated it looks fine:

  1. [Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression]
  2. .AsNoTracking()
  3. .Where(x => !x.IsDeleted && (x.BusinessId == AuthService.GetBusinessIdFromRequestDomain()))
  4. .Select(
  5. dtoLocation => new LocationResponse
  6. {
  7. Name = dtoLocation.Name,
  8. Address = (dtoLocation.Address == null)
  9. ? null
  10. : new AddressDetails
  11. {
  12. Country = dtoLocation.Address.Country,
  13. City = dtoLocation.Address.City,
  14. Postcode = dtoLocation.Address.Postcode,
  15. District = dtoLocation.Address.District,
  16. Street = dtoLocation.Address.Street,
  17. StreetNumber = dtoLocation.Address.StreetNumber,
  18. Building = dtoLocation.Address.Building,
  19. Entrance = dtoLocation.Address.Entrance,
  20. Floor = dtoLocation.Address.Floor,
  21. Apartment = dtoLocation.Address.Apartment,
  22. AdditionalInformation = dtoLocation.Address.AdditionalInformation
  23. }
  24. })

答案1

得分: 1

根据您的要求,以下是翻译好的部分:

在此问题得到修复之前,我找到了一个解决方法。

正如在此问题中所描述:https://github.com/dotnet/efcore/issues/30996。只有当所有属性都不为NULL时,它才会创建对象,因此我们可以在AutoMapper配置中添加DoNotAllowNull,这将始终创建一个新对象,无论对象的内容如何。

在AutoMapper配置中添加以下内容:

  1. CreateMap<Location, LocationResponse>()
  2. .ForMember(dest => dest.Address, opt => opt.DoNotAllowNull())

这将更改AutoMapper生成的表达式为:

  1. [Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression]
  2. .AsNoTracking()
  3. .Where(x => !x.IsDeleted && (x.BusinessId == AuthService.GetBusinessIdFromRequestDomain()))
  4. .Select(
  5. dtoLocation => new LocationResponse
  6. {
  7. Name = dtoLocation.Name,
  8. Address = new AddressDetails
  9. {
  10. Country = dtoLocation.Address.Country,
  11. City = dtoLocation.Address.City,
  12. Postcode = dtoLocation.Address.Postcode,
  13. District = dtoLocation.Address.District,
  14. Street = dtoLocation.Address.Street,
  15. StreetNumber = dtoLocation.Address.StreetNumber,
  16. Building = dtoLocation.Address.Building,
  17. Entrance = dtoLocation.Address.Entrance,
  18. Floor = dtoLocation.Address.Floor,
  19. Apartment = dtoLocation.Address.Apartment,
  20. AdditionalInformation = dtoLocation.Address.AdditionalInformation
  21. }
  22. })
英文:

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

  1. CreateMap&lt;Location, LocationResponse&gt;()
  2. .ForMember(dest =&gt; dest.Address, opt =&gt; opt.DoNotAllowNull())

This changes the Expression that AutoMapper generates to this

  1. [Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression]
  2. .AsNoTracking()
  3. .Where(x =&gt; !x.IsDeleted &amp;&amp; (x.BusinessId == AuthService.GetBusinessIdFromRequestDomain()))
  4. .Select(
  5. dtoLocation =&gt; new LocationResponse
  6. {
  7. Name = dtoLocation.Name,
  8. Address = new AddressDetails
  9. {
  10. Country = dtoLocation.Address.Country,
  11. City = dtoLocation.Address.City,
  12. Postcode = dtoLocation.Address.Postcode,
  13. District = dtoLocation.Address.District,
  14. Street = dtoLocation.Address.Street,
  15. StreetNumber = dtoLocation.Address.StreetNumber,
  16. Building = dtoLocation.Address.Building,
  17. Entrance = dtoLocation.Address.Entrance,
  18. Floor = dtoLocation.Address.Floor,
  19. Apartment = dtoLocation.Address.Apartment,
  20. AdditionalInformation = dtoLocation.Address.AdditionalInformation
  21. }
  22. })

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:

确定