设置AutoMapper约定以不合并ICollection属性

时间:2016-09-30 15:08:10

标签: c# automapper

说我有一个域名:

public class EmbeddedInBar
{
    public string Name { get; get; }
    public ICollection<int> ListOfInts { get; set; }
}

public class Bar
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<int> ListOfInts { get; set; }
    public EmbeddedInBar Embedded { get; set; }
}

我有两个现有的酒吧:

var destination = new Bar
{
    Id = 1,
    Name = "Destination",
    ListOfInts = new List<int>( 1,2,3 },
    Embedded = new EmbeddedInBar
    {
        Name = "DestinationEmbedded",
        ListOfInts = new List<int>( 4,5 }
    }
};

var source = new Bar
{
    Id = 2,
    Name = "Source",
    ListOfInts = new List<int>( 6,7,8 },
    Embedded = new EmbeddedInBar
    {
        Name = "SourceEmbedded",
        ListOfInts = new List<int>( 9,10 }
    }   
};

如果我做地图

Mapper.Initialize(conf =>
{
    conf.CreateMap<Bar, Bar>()
        .ForMember(b => b.Id, opts => opts.Ignore());
});

destination = Mapper.Instance.Map(source, destination);

我最终得到了可合并的可枚举属性:

{
    Id: 1,
    Name: "Source",
    ListOfInts: [ 1,2,3,6,7,8 ]
    Embedded: {
        Name: "SourceEmbedded",
        ListOfInts: [ 9,10 ]
    }
}

是否可以在AutoMapper中设置约定(而不是特定的&#39; ForMember&#39;语句,假设在编译时我不知道属性名称/表达式) 丢弃目标ICollection值并用源值覆盖它们?所以我最终得到了:

{
    Id: 1,
    Name: "Source",
    ListOfInts: [ 6,7,8 ]
    Embedded: {
        Name: "SourceEmbedded",
        ListOfInts: [ 9,10 ]
    }
}

2 个答案:

答案 0 :(得分:1)

您可以使用AutoMapper过滤来执行此操作:https://github.com/AutoMapper/AutoMapper/wiki/Configuration#global-propertyfield-filtering

对于您的示例,您可以使用以下内容:

        Mapper.Initialize(expression =>
        {
            expression.ShouldMapProperty = info => !(
                info.PropertyType.IsGenericType && 
                info.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>));

            expression.CreateMap<Bar, Bar>();
            expression.CreateMap<EmbeddedInBar, EmbeddedInBar>();
        });

        Mapper.Map(source, destination);

修改

抱歉,我一开始并不理解你。我为你的问题找到了解决方案。您可以使用Automapper自定义类型转换器: https://github.com/AutoMapper/AutoMapper/wiki/Custom-type-converters

整个解决方案看起来像这样:

创建集合类型转换器:

class CollectionTypeConverter<T> : ITypeConverter<ICollection<T>, ICollection<T>>
{
    public ICollection<T> Convert(ICollection<T> source, ICollection<T> destination, ResolutionContext context)
    {
        return source;
    }
}

将其包含在映射器初始化中:

Mapper.Initialize(expression =>
        {
            expression.CreateMap(typeof(ICollection<>), typeof(ICollection<>)).ConvertUsing(typeof(CollectionTypeConverter<>));

            expression.CreateMap<Bar, Bar>();
            expression.CreateMap<EmbeddedInBar, EmbeddedInBar>();
        });

答案 1 :(得分:1)

我发现的解决方案可能会有一些无法预料的结果 - 我没有足够的经验AutoMapper事先想好所有事情,所以请仔细使用。从表面上看,它不应该影响你问题中描述的任何其他内容。

AutoMapper有一个名为Mappers的功能,存储在静态集合AutoMapper.Mappers.MapperRegistry.Mappers中。里面的类能够改变对象的映射方式。默认情况下,集合包含大量不同的映射器(see source),其中可能找到CollectionMappersee source)。如果它不是只读的,则此映射器能够将源集合中的项目添加到目标集合。您可以做的是在初始化映射器之前通过运行此代码将其从映射器集合中删除:

    var collectionMapper = MapperRegistry.Mappers.OfType<CollectionMapper>().Single();
    MapperRegistry.Mappers.Remove(collectionMapper);

删除CollectionMapper不会删除映射集合的功能,这将由名为EnumerableMappersee source)的第二个映射器处理。它们之间的差异在line 26中可见 - 这个映射器在映射时不使用目标属性 - 它创建了一个新的集合,其中填充了源集合中的项目。

我在本地使用AutoMapper 5.1.1和您提供的课程进行了测试。结果如您所愿。