我正在尝试在我的项目中实现 AutoMapper。它通过在服务中使用 DTO 来正常运行。我能够将 AutoMapper 实现为简单的 DTO,但是我不知道如何将其实现为通过多个实体之间的关系进行导航的 DTO。请耐心听我解释其中的关系:
我有一个
Artist
实体。该艺术家可能在乐队中演奏,并且通过 Band
表连接到 Role
实体。在 Role
表中,我们有艺术家和乐队实体,并且该特定乐队中艺术家演奏的乐器存储在名为 RoleInstruments
的表中。我有一个名为 ArtistRoleDto
的 DTO,我将其用于多种目的。该 DTO 包含艺术家、乐队和乐器信息。有时我用它来查找单个乐队中的多个艺术家,有时我用它来查找同一艺术家的多个角色(哪个乐器在哪个乐队中演奏)。
用例的多样性让我感到困惑,我不知道如何在定义映射器时浏览必要的关系表。
这是我希望完成的代码。
CreateMap<ArtistRoleDto, Artist>()
.ForMember(
dest => dest.Id,
opt => opt.MapFrom(src => $"{src.ArtistId}")
)
.ForMember(
dest => dest.FirstName,
opt => opt.MapFrom(src => $"{src.FirstName}")
)
.ForMember(
dest => dest.LastName,
opt => opt.MapFrom(src => $"{src.LastName}")
)
.ForMember(
dest => dest.Genre,
opt => opt.MapFrom(src => $"{src.ArtistGenre}")
)
.ForMember(
// BandId - through Roles
)
.ForMember(
// BandName - through Roles.Band
)
.ForMember(
// BandGenre - through Roles.Band
)
.ForMember(
// Instrument - through Roles.RoleInstruments (both lists)
);
以下是相关实体的定义:
public class Artist
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Genre { get; set; }
public string? Description { get; set; }
public ICollection<Role> Roles { get; set; }
}
public class Role
{
[Key]
public int Id { get; set; }
// Foreign Key to Artist Table
public int ArtistId { get; set; }
public Artist Artist { get; set; }
// Foreign Key to Band Table
public int BandId { get; set; }
public Band Band { get; set; }
// Additional properties related to the role, e.g., RoleDescription
public string? Description { get; set; }
// Collection of RoleInstruments associated with the role
public ICollection<RoleInstruments> RoleInstruments { get; set; }
}
public class RoleInstruments
{
// Foreign Key to Role Table
public int RoleId { get; set; }
public Role Role { get; set; }
// Foreign Key to Instrument Table
public int InstrumentId { get; set; }
public Instrument Instrument { get; set; }
}
我尝试了不同的方法,但老实说我感觉很无能。我首先想到的是:
.ForMember(
dest => dest.Roles.Select(r => r.BandId),
opt => opt.MapFrom(src => $"{src.BandId}")
)
.ForMember(
// BandName - through Roles.Band
dest => dest.Roles.Select(r => r.Band.Name),
opt => opt.MapFrom(src => $"{src.BandName}")
)
.ForMember(
// BandGenre - through Roles.Band
dest => dest.Roles.Select(r => r.Band.Genre),
opt => opt.MapFrom(src => $"{src.BandGenre}")
)
.ForMember(
// Instrument - Through Roles.RoleInstruments (both lists)
dest => dest.Roles.SelectMany(r => r.RoleInstruments.Select(ri => ri.Instrument.Name),
opt => opt.MapFrom(src => $"{src.Instrument}")
));
然后我尝试了我心爱的 ChatGPT 的一些推荐,我得到的最接近的是:
.ForMember(
dest => dest.Roles,
opt => opt.MapFrom(src => new List<Role>
{
new Role
{
BandId = src.BandId,
Band = new Band
{
Name = src.BandName,
Genre = src.BandGenre
},
RoleInstruments = src.Instrument.Split(", ").Select(instrumentName => new RoleInstruments
{
Instrument = new Instrument
{
// Here I get an error saying I can't convert char (instrumentName) to str.
Name = instrumentName
}
}).ToList()
所以我现在认为解决方案可能包括在配置文件中创建一个新的
Role
?
或者考虑到 ArtistRoleDto 在各种上下文中使用,也许我应该在不同的配置文件中定义不同的映射器?当在乐队服务中使用它时,我可以按照 BandProfile 中定义的那样使用它吗?
我感谢所有花时间调查这个问题的人。
因此,这个问题的解决方案实际上是手动映射这个特定对象,正如问题评论中所建议的那样。这是因为关系太复杂,无法使用 AutoMapper 来处理。然而,当使用 AutoMapper 遇到类似问题时,我发现为了在关系中移动时不遇到问题,我所要做的就是在定义要在服务中映射的对象时包含属性。例如:
public async Task<List<RoleInBandDto>> GetRoles(int bandId)
{
var roleInstruments = await _context.RoleInstruments
.Where(ri => ri.Role.Band.Id == bandId)
.Include(ri => ri.Instrument)
.Include(ri => ri.Role).ThenInclude(r => r.Artist)
.Include(ri => ri.Role).ThenInclude(r => r.Band).ToListAsync();
var roles = _mapper.Map<List<RoleInBandDto>>(roleInstruments);
return roles;
}
我确保包含 AutoMapper 必须导航的所有表,这些表是在此处定义的:
CreateMap<RoleInstruments, RoleInBandDto>()
.ForMember(
dest => dest.BandName,
opt => opt.MapFrom(src => src.Role.Band.Name))
.ForMember(
dest => dest.ArtistFirstName,
opt => opt.MapFrom(src => src.Role.Artist.FirstName))
.ForMember(
dest => dest.ArtistLastName,
opt => opt.MapFrom(src => src.Role.Artist.LastName))
.ForMember(
dest => dest.Instrument,
opt => opt.MapFrom(src => src.Role.RoleInstruments.Select(ri => ri.Instrument.Name)));