使用Automapper映射自引用关系

时间:2019-04-13 20:06:22

标签: c# asp.net-core automapper

我有一个涉及.NET Core ApplicationUser的自引用关系:

public class Network
{
    public ApplicationUser ApplicationUser { get; set; }
    public string ApplicationUserId { get; set; }
    public ApplicationUser Follower { get; set; }
    public string FollowerId { get; set; }
}

这使得可以在用户模型中保留“关注者”和“关注者”列表:

public class ApplicationUser : IdentityUser
{
    //...
    public ICollection<Network> Following { get; set; }
    public ICollection<Network> Followers { get; set; }
}

我使用Automapper将关注者和关注列表映射到视图模型。这是视图模型:

public class UserProfileViewModel
{
    //...
    public IEnumerable<FollowerViewModel> Followers { get; set; }
    public IEnumerable<NetworkUserViewModel> Following { get; set; }
}

public class NetworkUserViewModel
{
    public string UserName { get; set; }
    public string ProfileImage { get; set; }
    public bool IsFollowing { get; set; } = true;
    public bool IsOwnProfile { get; set; }
}

public class FollowerViewModel
{
    public string UserName { get; set; }
    public string ProfileImage { get; set; }
    public bool IsFollowing { get; set; } = true;
    public bool IsOwnProfile { get; set; }
}

要考虑跟随者和跟随者的映射方式不同,我不得不创建两个相同的类:NetworkUserViewModelFollowerViewModel,以便Automapper映射逻辑可以区分如何映射跟随者和对象。如何映射遵循。这是映射配置文件:

         CreateMap<Network, NetworkUserViewModel>()
         .ForMember(x => x.UserName, y => y.MapFrom(x => x.ApplicationUser.UserName))
         .ForMember(x => x.ProfileImage, y => y.MapFrom(x => x.ApplicationUser.ProfileImage))
         .ReverseMap();

        CreateMap<Network, FollowerViewModel>()
        .ForMember(x => x.UserName, y => y.MapFrom(x => x.Follower.UserName))
        .ForMember(x => x.ProfileImage, y => y.MapFrom(x => x.Follower.ProfileImage))
        .ReverseMap();

        CreateMap<ApplicationUser, UserProfileViewModel>()
          .ForMember(x => x.UserName, y => y.MapFrom(x => x.UserName))
          .ForMember(x => x.Followers, y => y.MapFrom(x => x.Followers))
          .ForMember(x => x.Following, y => y.MapFrom(x => x.Following))
          .ReverseMap();

您可以看到关注者的映射类似于.MapFrom(x => x.Follower.UserName)),而关注用户的映射则类似于.MapFrom(x => x.ApplicationUser.UserName))

我的问题是:是否可以定义映射关注者和关注用户的不同方法,而不必定义重复的类?我更愿意将NetworkUserViewModel用于关注的关注者;因此,如果可能的话,我不需要重复的FollowerViewModel。有办法吗?

更新

我已经实现了@Matthijs的建议(请参阅第一个评论),以使用继承来避免不必要的属性重复。我的视图模型现在是这些:

public class UserProfileViewModel
{
    //...
    public IEnumerable<FollowerViewModel> Followers { get; set; }
    public IEnumerable<FollowingViewModel> Following { get; set; }
}

public class NetworkUserViewModel
{
    public string UserName { get; set; }
    public string ProfileImage { get; set; }
    public bool IsFollowing { get; set; }
    public bool IsOwnProfile { get; set; }
}

public class FollowingViewModel : NetworkUserViewModel
{
}

public class FollowerViewModel : NetworkUserViewModel
{
}

对Automapper逻辑进行以下更改:

         CreateMap<Network, FollowingViewModel>()
         .ForMember(x => x.UserName, y => y.MapFrom(x => x.ApplicationUser.UserName))
         .ForMember(x => x.ProfileImage, y => y.MapFrom(x => x.ApplicationUser.ProfileImage))
         .ReverseMap();

        CreateMap<Network, FollowerViewModel>()
        .ForMember(x => x.UserName, y => y.MapFrom(x => x.Follower.UserName))
        .ForMember(x => x.ProfileImage, y => y.MapFrom(x => x.Follower.ProfileImage))
        .ReverseMap();

        CreateMap<ApplicationUser, UserProfileViewModel>()
          .ForMember(x => x.UserName, y => y.MapFrom(x => x.UserName))
          .ForMember(x => x.Followers, y => y.MapFrom(x => x.Followers))
          .ForMember(x => x.Following, y => y.MapFrom(x => x.Following))
          .ReverseMap();

此重构减少了重复,并使映射逻辑更易于遵循。

为了让解决方案完全依赖于Automapper逻辑,我将开放几天的时间...

1 个答案:

答案 0 :(得分:1)

您遇到问题的原因是目标对象相同,但 Automapper 无法推断它。 Automapper 只是使用反射来查找具有相同名称的变量。

对于复杂的映射,下次可以考虑编写自定义映射器。这为您提供了极大的灵活性,并简化了您的映射配置。您可以创建自定义映射解析器以从任何来源返回您需要的对象,即使您指定了多个来源。您将源映射到您自己的目标成员。我发现这非常好,将来可能会对您有所帮助。它是这样工作的:

解析器:

public class MappingResolver: IValueResolver<object, object, MyResponseObject>
{
    // Automapper needs parameterless constructor
    public MappingResolver()
    {
    }

    // Resolve dependencies here if needed
    public MappingResolver(IWhateverINeed whateverINeed)
    {
}

    public MyResponseObject Resolve(
        object source, object destination, MyResponseObject> destMember, ResolutionContext context)
    {
        if (source.GetType() == typeof(NetworkUserViewModel) 
        {
          // Specific logic for source object, while destination remains the same response
          var castedObject = source as NetworkUserViewModel;
          return MyResponseObject;
      }

}

并像这样将其添加到 Mapper 配置中:

CreateMap<SourceObjectA, MyResponseObject>()
 .ForMember(dest => dest.ObjectA, src => src.MapFrom<MappingResolver>());

CreateMap<SourceObjectB, MyResponseObject>()
 .ForMember(dest => dest.ObjectB, src => src.MapFrom<MappingResolver>());