英文:
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
}
})
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论