设置一个AutoMapper约定,不合并集合属性

本文关键字:合并 集合 属性 约定 AutoMapper 一个 设置 | 更新日期: 2023-09-27 18:09:36

假设我有一个域:

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

我有两个现有的bar:

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

如果我做一个map

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中设置约定(而不是特定的' remember '语句,假设在编译时我不知道属性名称/表达式)丢弃目标集合值并用源值覆盖它们?所以最后是:

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

设置一个AutoMapper约定,不合并集合属性

您可以使用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>();
        });

我发现的解决方案可能会有一些不可预见的结果-我没有足够的AutoMapper经验来事先考虑所有事情,所以请小心使用它。从表面上看,它不应该影响你问题中所描述的其他任何事情。

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

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

删除CollectionMapper不会删除映射集合的功能,这将由第二个名为EnumerableMapper的映射器处理(参见源代码)。它们之间的区别在第26行可以看到——这个映射器在映射时不使用destination属性——它创建了一个新的集合,其中填充了来自源集合的项。

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