简体   繁体   English

在Automapper中映射集合

[英]Mapping Collections in Automapper

I'm using Automapper to clone an object. 我正在使用Automapper克隆对象。 I have a class which contains collections that I want handled in a non-standard way, which I'll explain below (I've stripped out a bunch of code to highlight the specific issue): 我有一个类,其中包含要以非标准方式处理的集合,我将在下面进行解释(我剥离了一些代码来突出显示特定问题):

public class CommunityModel 
{
    private readonly IUIManaer _uiMgr;
    private readonly IMapper _mapper;

    private ValidatedCollection<CommunityUserModel, string> _users;
    private int _communityIndex = -1;

    public CommunityModel( IUIManager uiMgr, IMapper mapper, IElementManager<CommunityUserModel, string> userMgr )
    {
        _uiMgr = uiMgr ?? throw new NullReferenceException( nameof(uiMgr) );
        _mapper = mapper ?? throw new NullReferenceException( nameof(mapper) );

        Users = new ValidatedCollection<CommunityUserModel, string>( userMgr );

    }

    public int CommunityIndex
    {
        get => _communityIndex;

        set
        {
            if (value < -1) value = -1;

            Set(ref _communityIndex, value);

            IsNew = value < 0;
        }
    }

    public ValidatedCollection<CommunityModel, string> Collection { get; set; }

    public ValidatedCollection<CommunityUserModel, string> Users
    {
        get => _users;

        set
        {
            ChangeTracker.RegisterCollection(value);

            SetAndValidate( ref _users, value );
        }
    }
}

ValidatedCollection<> is an extension of WPF's ObservableCollection. ValidatedCollection <>是WPF的ObservableCollection的扩展。 The CommunityIndex property uniquely identifies a CommunityModel instance. CommunityIndex属性唯一地标识CommunityModel实例。 This allows me to use the Automapper.Collection extensions via the EqualityComparison() extension method. 这使我可以通过EqualityComparison()扩展方法使用Automapper.Collection扩展。

I don't want Automapper to initialize the Users collection, because it gets initialized in the class constructor. 我不希望Automapper初始化Users集合,因为它是在类构造函数中初始化的。 But I want the elements of the User collection to be cloned from the source Users collection to the destination Users collection. 但是我希望将User集合的元素从源Users集合克隆到目标Users集合。

The Collection collection contains a list of CommunityModel objects, including the instance which has the Collection property (ie, Collection is a set of sibling CommunityModel instances). Collection集合包含CommunityModel对象的列表,包括具有Collection属性的实例(即Collection是一组同级CommunityModel实例)。 I'd like Automapper to initialize the collection, and, ultimately, copy all of the CommunityModel siblings other than the source object to that collection, and then add the destination object to the collection (ie, in the end, Collection will be a set of sibling CommunityModel objects, with the source CommunityModel replaced by the destination CommunityModel). 我希望Automapper初始化集合,并最终将源对象之外的所有CommunityModel兄弟复制到该集合中,然后将目标对象添加到该集合中(即,最后,集合将是一个集合同胞CommunityModel对象,而源CommunityModel被目标CommunityModel代替了)。

My map is currently defined as follows: 我的地图当前定义如下:

CreateMap<CommunityModel, CommunityModel>()
    .ForMember(dest=>dest.Users, opt=>opt.Ignore())
    .ForMember(dest=>dest.Collection, opt=>opt.Ignore()  )
    .EqualityComparison((x,y)=> x.CommunityIndex == y.CommunityIndex);

CreateMap<CommunityUserModel, CommunityUserModel>()
    .EqualityComparison((x,y) => x.UserIndex == y.UserIndex);

If I don't Ignore() the Users collection, Automapper will initialize the collection, which overrides the required configuration of Users set in the constructor and causes problems elsewhere in my app. 如果我不忽略()Users集合,则Automapper将初始化该集合,这将覆盖构造函数中设置的Users所需的配置,并在我的应用程序中的其他地方引起问题。 But if Ignore() the Users collection, its elements are never cloned from source to destination. 但是,如果忽略Ignore()Users集合,则永远不会将其元素从源克隆到目标。 What I want to do is not have Automapper initialize Users, but still clone the contents. 我想要做的是没有Automapper初始化用户,但仍克隆内容。

If I don't ignore the Collection collection, I get an infinite loop and stack overflow, I believe because cloning Collection's elements involves creating an instance of the CommunityModel which owns the Collection property, which should be a reference to the destination object being created, but instead leads to the creation of another identical destination object. 如果我不忽略Collection集合,则会遇到无限循环和堆栈溢出的情况,我相信,因为克隆Collection的元素涉及到创建拥有Model属性的CommunityModel实例,该实例应该是对要创建的目标对象的引用,而是导致创建另一个相同的目标对象。 What I'd like to do is have Automapper initialize the Collection collection, but >>not<< clone the source elements, which I guess I'd have to do later in an AfterMap() call. 我想做的是让Automapper初始化Collection集合,但是>> not <<克隆源元素,我想稍后在AfterMap()调用中必须这样做。

I realize this is somewhat arcane, but the overall design of my project results in these requirements. 我意识到这有些不可思议,但是我项目的总体设计导致了这些需求。

What is the best way of doing this within Automapper? 在Automapper中执行此操作的最佳方法是什么? Should I look into creating a custom value resolver, even though I'm cloning an object, so the property names are identical between source and destination? 即使我正在克隆对象,我是否也应该考虑创建自定义值解析器,因此源和目标之间的属性名称相同?

I'm going to describe how I resolved my issue, but I'm not going to mark it as an answer, because I'm not familiar enough with Automapper to know if what I think it's doing is what it's actually doing. 我将描述如何解决我的问题,但我不会将其标记为答案,因为我对Automapper不够熟悉,无法知道我认为它在做什么,而实际上是在做什么。

What appears to be happening, when Automapper maps collections -- even with the Automapper.Collections library installed and activated -- is that collections are deemed to be "different", even if the types of the elements in the source and destination collections can be automatically mapped, if the source and destination collection types are different. 当Automapper映射集合(即使已安装并激活Automapper.Collections库)时,似乎正在发生的事情是,即使源集合和目标集合中元素的类型可以是“不同”,也认为集合是“不同的”如果源和目标集合类型不同,则自动映射。

For example, if the source Community object has a List<> of CommunityUsers: 例如,如果源社区对象具有CommunityUsers的List <>:

public class Community
{
    public string Name { get; set; }
    public string SiteUrl { get; set; }
    public string LoginUrl { get; set; }
    public List<CommunityUser> Users { get; set; }
}

public class CommunityUser
{
    public string UserID { get; set; }
    public string VaultKeyName { get; set; }
}

and you want to map it to destination objects like this: 并且您想要将其映射到目标对象,如下所示:

public class CommunityModel
{
    // omitting constructor, which contains logic to 
    // initialize the Users property based on constructor arguments

    public string Name {get;set;}
    public string LoginUrl {get;set;}
    public string SiteUrl {get;set;}
    public ValidatedCollection<CommunityUserModel, string> Users
    { get; set;}
}

public class CommunityUserModel 
{
    public string UserID {get; set;}
    public string VaultKeyName {get;set;}
}

even though all the property names are "recognizable" by Automapper, and the two Users collections are both IEnumerable, the fact that the two collections are of different types apparently causes Automapper to treat the collections as "different". 即使所有属性名称都可以由Automapper识别,并且两个Users集合都是IEnumerable,但是两个集合类型不同的事实显然会使Automapper将这些集合视为“不同”。

And that apparently means the add/delete/copy logic of Automapper.Collections doesn't get used, even if present. 显然,这意味着Automapper.Collections的添加/删除/复制逻辑不会被使用,即使存在也是如此。 Instead, Automapper tries to create an instance of (in this case) ValidatedCollection, populate it from the source object collection, and then assign it to destination object collection. 而是,Automapper尝试创建(在本例中)ValidatedCollection的实例,从源对象集合填充它,然后将其分配给目标对象集合。

That's fine if ValidatedCollection<> doesn't have required contructor arguments. 如果ValidatedCollection <>没有必需的构造方法参数,那很好。 But it'll fail if it does. 但是,如果这样做,它将失败。 Which is what happened in my case. 我的情况就是这样。

My workaround was to do this in the Mapper definition: 我的解决方法是在Mapper定义中执行此操作:

CreateMap<Community, CommunityModel>()
    .ForMember(dest=>dest.Users, opt=> opt.Ignore())
    .AfterMap( ( src, dest, rc ) =>
    {
        foreach( var srcUser in src.Users )
        {
            dest.Users.Add(rc.Mapper.Map<CommunityUserModel>(srcUser));
        }
    } );

This keeps Automapper from doing anything with the destination Users property (which is initialized in the CommunityModel constructor), and maps over the source User objects after the "automatic" mapping is done. 这使Automapper不会对目标Users属性(在CommunityModel构造函数中初始化)进行任何操作,并在完成“自动”映射后在源User对象上进行映射。

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

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