5

我在使用 automapper 映射我的父类时遇到了一些问题。给定以下类,我创建了一个映射配置文件。

映射类:

public class SourceClass
{
    public int SourceProperty1 { get; set; }
    public int SourceProperty2 { get; set; }
    public string SourceProperty3 { get; set; }
    public string SourceProperty4 { get; set; }
}

public class TargetBaseClass
{
    public int TargetProperty1 { get; set; }
    public int TargetProperty2 { get; set; }
}

public class TargetClass1: TargetBaseClass
{
    public string TargetProperty3 { get; set; }
}

public class TargetClass2: TargetBaseClass
{
    public string TargetProperty4 { get; set; }
}

地图:

public class MappingProfile: Profile
{
    protected override void Configure()
    {
        CreateMap<SourceClass, TargetBaseClass>()
            .Include<SourceClass, TargetClass1>()
            .Include<SourceClass, TargetClass2>()
            .ForMember(dst => dst.TargetProperty1, opt => opt.MapFrom(src => src.SourceProperty1))
            .ForMember(dst => dst.TargetProperty2, opt => opt.MapFrom(src => src.SourceProperty2));
        CreateMap<SourceClass, TargetClass1>()
            .ForMember(dst => dst.TargetProperty3, opt => opt.MapFrom(src => src.SourceProperty3));
        CreateMap<SourceClass, TargetClass2>()
            .ForMember(dst => dst.TargetProperty4, opt => opt.MapFrom(src => src.SourceProperty4));
    }
}

最后是我的程序:

static void Main(string[] args)
{
    Mapper.Initialize(x => x.AddProfile<MappingProfile>());
    var sourceClass = new SourceClass
        {
            SourceProperty1 = 1,
            SourceProperty2 = 2,
            SourceProperty3 = "3",
            SourceProperty4 = "4"
        };
    var targetBaseClass = Mapper.Map<TargetBaseClass>(sourceClass);
    var targetClass1 = Mapper.Map<TargetClass1>(sourceClass);
    var targetClass2 = Mapper.Map<TargetClass2>(sourceClass);

    Console.WriteLine("TargetBaseClass: {0} {1}", targetBaseClass.TargetProperty1,
                      targetBaseClass.TargetProperty2); //1 2
    Console.WriteLine("TargetClass1: {0} {1} {2}", targetClass1.TargetProperty1, targetClass1.TargetProperty2,
                      targetClass1.TargetProperty3);//0 0 3 ???
    Console.WriteLine("TargetClass2: {0} {1} {2}", targetClass2.TargetProperty1, targetClass2.TargetProperty2,
                      targetClass2.TargetProperty4);//1 2 4
}

问题是,当我尝试映射到派生类时,我的父类的属性不会被映射,但TargetClass1映射. TargetClass2谁能向我解释我做错了什么,为什么这两张地图表现不同?(我的顺序Include重要吗?)

编辑:仔细检查,订单确实很重要。但是,我仍然不知道为什么只有第二个Include才算数。

Edit2:根据@GruffBunny 的评论,我想我可以通过使用扩展方法来“修复”这个问题。但是,我真的不明白他们为什么这样做。查看 的代码AutoMapper.TypeMap,我可以清楚地看到:

public void IncludeDerivedTypes(Type derivedSourceType, Type derivedDestinationType)
{
    _includedDerivedTypes[derivedSourceType] = derivedDestinationType;
}

显然,这意味着您只能为每个包含的源类型指定一个目标。但是,我看不到任何东西,这会阻止他们支持多个目标类型。

4

2 回答 2

5

你可以看看这个自定义扩展方法。源代码也可以在 Github 上找到

扩展方法有我现在能想到的树的缺点。第一个是Mapper.AssertConfigurationIsValid()它将失败,因为它找不到在基本映射中定义的属性映射。对此的解决方案是忽略基本映射中定义的任何提供的成员映射。

二是扩展方法依赖于静态Mapper类。如果这是您使用 AutoMapper 的方式,那么就没有问题。如果您有多个映射引擎和/或针对 AutoMapper 的接口编写代码,则不能使用此扩展方法。为了支持这两种情况,我们需要添加两个可选参数:IConfigurationProviderIMappingEngine.

我尽量避免使用静态Mapper类,并通过IoC容器将接口注入到我需要的地方。

第三是扩展方法不返回IMappingExpression<TSource, TDestination>并禁止覆盖基成员映射。为了解决这个问题,我们返回 IMappingExpression<TSource, TDestination>并删除所有成员的条件。

这导致以下代码:

public enum WithBaseFor
{
    Source,
    Destination,
    Both
}

public static class AutoMapperExtensions
{
    public static IMappingExpression<TSource, TDestination> InheritMappingFromBaseType<TSource, TDestination>(
        this IMappingExpression<TSource, TDestination> mappingExpression,
        WithBaseFor baseFor = WithBaseFor.Both,
        IMappingEngine mappingEngine = null,
        IConfigurationProvider configurationProvider = null)
    {
        Type sourceType = typeof (TSource);
        Type destinationType = typeof (TDestination);

        Type sourceParentType = baseFor == WithBaseFor.Both || baseFor == WithBaseFor.Source
            ? sourceType.BaseType
            : sourceType;

        Type destinationParentType = baseFor == WithBaseFor.Both || baseFor == WithBaseFor.Destination
            ? destinationType.BaseType
            : destinationType;

        mappingExpression
            .BeforeMap((sourceObject, destObject) =>
            {
                if (mappingEngine != null)
                    mappingEngine.Map(sourceObject, destObject, sourceParentType, destinationParentType);
                else
                    Mapper.Map(sourceObject, destObject, sourceParentType, destinationParentType);
            });

        TypeMap baseTypeMap = configurationProvider != null
            ? configurationProvider.FindTypeMapFor(sourceParentType, destinationParentType)
            : Mapper.FindTypeMapFor(sourceParentType, destinationParentType);

        if (baseTypeMap == null)
        {
            throw new InvalidOperationException(
                string.Format("Missing map from {0} to {1}.", new object[]
                {
                    sourceParentType.Name,
                    destinationParentType.Name
                }));
        }

        foreach (PropertyMap propertyMap in baseTypeMap.GetPropertyMaps())
            mappingExpression.ForMember(propertyMap.DestinationProperty.Name, opt => opt.Ignore());

        return mappingExpression;
    }
}

用法

CreateMap<SourceClass, TargetBaseClass>()
    .ForMember(dst => dst.TargetProperty1, opt => opt.MapFrom(src => src.SourceProperty1))
    .ForMember(dst => dst.TargetProperty2, opt => opt.MapFrom(src => src.SourceProperty2));

CreateMap<SourceClass, TargetClass1>()
    .ForMember(dst => dst.TargetProperty3, opt => opt.MapFrom(src => src.SourceProperty3))
    .InheritMappingFromBaseType(WithBaseFor.Destination)
    ;

CreateMap<SourceClass, TargetClass2>()
    .ForMember(dst => dst.TargetProperty4, opt => opt.MapFrom(src => src.SourceProperty4))
    .InheritMappingFromBaseType(WithBaseFor.Destination)
    ;

也许还有一些场景没有被涵盖,但它肯定会解决你的问题,这样你就不需要编写特定的扩展方法。

于 2013-10-18T21:51:24.427 回答
3

我最终以这里这里描述的方式创建了一个扩展方法。

public static class Extensions
{
    public static IMappingExpression<SourceClass, TDestination> MapBase<TDestination>(
        this IMappingExpression<Source, TDestination> mapping)
        where TDestination: TargetBaseClass
    {
        // all base class mappings goes here
        return mapping
            .ForMember(dst => dst.TargetProperty1, opt => opt.MapFrom(src => src.SourceProperty1))
            .ForMember(dst => dst.TargetProperty2, opt => opt.MapFrom(src => src.SourceProperty2));
    }
}

我仍然不确定为什么这条线不允许在 aIDictionary<type, IEnumerable<type>>虽然中指定多种类型。我敢肯定 AutoMapper 的人有他们的理由。

于 2013-10-18T15:06:04.290 回答