简体   繁体   English

使用 Automapper 映射自引用关系

[英]Using Automapper to map self referencing relationship

I have a self-referencing relationship involving .NET Core's ApplicationUser :我有一个涉及 .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; }
}

This makes it possible to keep a list of 'followers' and 'following' in the user model:这使得在用户模型中保留“关注者”和“关注者”列表成为可能:

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

I use Automapper to map the followers and followed lists to a viewmodel.我使用 Automapper 将关注者和关注列表映射到视图模型。 Here are the viewmodels:以下是视图模型:

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; }
}

To take account of the different ways that followers and followed are mapped, I have had to create two identical classes: NetworkUserViewModel and FollowerViewModel so that the Automapper mapping logic can distinguish how to map followers and how to map followed.为了考虑到FollowerViewModelFollowerViewModel的不同映射方式,我必须创建两个相同的类: NetworkUserViewModelFollowerViewModel以便 Automapper 映射逻辑可以区分如何映射关注者和如何映射关注者。 Here is the mapping profile:这是映射配置文件:

         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();

You can see that followers are mapped like .MapFrom(x => x.Follower.UserName)) while following users are mapped like .MapFrom(x => x.ApplicationUser.UserName)) .您可以看到关注者被映射为.MapFrom(x => x.Follower.UserName))而关注用户被映射为.MapFrom(x => x.ApplicationUser.UserName))

My question is: can I define the different ways of mapping followers and followed users without having to define duplicate classes?我的问题是:我可以定义映射关注者和关注用户的不同方式而不必定义重复的类吗? I would prefer to use the NetworkUserViewModel for followed and followers;我更喜欢将NetworkUserViewModel用于关注者关注者; so that I do not need the duplicate FollowerViewModel , if possible.所以如果可能的话,我不需要重复的FollowerViewModel Is there a way to do it?有没有办法做到这一点?

Update更新

I have implemented the suggestion by @Matthijs (see first comment) to use inheritence to avoid the unnecessary duplication of properties.我已经实现了@Matthijs(见第一条评论)的建议,使用继承来避免不必要的属性重复。 My viewmodels are now these:我的视图模型现在是这些:

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
{
}

With the following change to the Automapper logic:对 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();

This refactor reduces the duplication and makes the mapping logic easier to follow.这种重构减少了重复并使映射逻辑更易于遵循。

I'm leaving this open for a few days just in case there is a solution relying purely on the Automapper logic...我将这个开放几天,以防万一有一个纯粹依赖于 Automapper 逻辑的解决方案......

The reason you ran into problems is because the destination object is the same, but Automapper cannot infer it.您遇到问题的原因是目标对象相同,但 Automapper 无法推断出它。 Automapper simply uses reflection to find variables with the same name. Automapper 只是使用反射来查找具有相同名称的变量。

For complicated mappings, next time, you can consider writing a custom mapper.对于复杂的映射,下次可以考虑编写自定义映射器。 This gives you great flexibility and it simplifies your mapping configuration.这为您提供了极大的灵活性,并简化了您的映射配置。 You can create a custom mapping resolver to return the object you need from any source, even if you specify multiple sources.您可以创建自定义映射解析器以从任何来源返回您需要的对象,即使您指定了多个来源。 You map the source to your own destination member.您将源映射到您自己的目标成员。 I found this to be really nice and it might help you in the future.我发现这非常好,将来可能会对您有所帮助。 It works like this:它是这样工作的:

Resolver:解析器:

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;
      }

} }

And add it like this to Mapper config:并将其添加到 Mapper 配置中:

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM