简体   繁体   English

Automapper映射具有继承性的基类

[英]Automapper Mapping base classes with inheritence

I'm having some trouble mapping my parent class using automapper. 我在使用automapper映射我的父类时遇到了一些麻烦。 Given the following classes, I have created a mapping profile. 鉴于以下类,我创建了一个映射配置文件。

Mapping classes: 映射类:

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

Map: 地图:

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

And finally my program: 最后我的计划:

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
}

The problem is, when I try mapping to the derived classes, my parent class' properties won't get mapped in case of TargetClass1 but it will for TargetClass2 . 问题是,当我尝试映射派生类,我的父类的属性将不会在以下情况下映射TargetClass1但它TargetClass2 Can anyone explain to me what it is I'm doing wrong, and why these 2 maps are acting differently? 任何人都可以向我解释我做错了什么,为什么这两张地图的行为不同? (Does the order in which I Include matter?) (我Include的顺序是否重要?)

Edit: On close inspection, the order does indeed matter. 编辑:在仔细检查,秩序确实重要。 However, I still don't know why only the second Include would count. 但是,我仍然不知道为什么只有第二个Include才算数。

Edit2: Based on the comment by @GruffBunny, I think I could "fix" this by using an Extension Method. Edit2:基于@GruffBunny的评论,我想我可以通过使用扩展方法“修复”这个问题。 However, I don't really get why they made it this way. 但是,我真的不明白为什么他们这样做了。 Looking at the code for AutoMapper.TypeMap , I can clearly see this: 查看AutoMapper.TypeMap的代码,我可以清楚地看到:

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

Obviously this means you can only specify one destination per included source type. 显然,这意味着您只能为每个包含的源类型指定一个目标。 I don't see anything, that would prevent them from supporting more than one destinationtype, however. 但是,我没有看到任何东西,这会阻止它们支持多个目标类型。

You can take a look at this custom extension method . 您可以查看此自定义扩展方法 The source code can also be found on Github here . 源代码也可以在Github上找到

The extension method has tree downsides that I can think of right now. 扩展方法有树的缺点,我现在可以想到。 The first is that the Mapper.AssertConfigurationIsValid() will fail because it will not find the property mappings defined in the base map. 第一个是Mapper.AssertConfigurationIsValid()将失败,因为它将找不到基本映射中定义的属性映射。 The solution to this would be to ignore any of the provided member mappings defined in the base map. 解决方案是忽略基本映射中定义的任何提供的成员映射。

The second is that the extension method depends on the the static Mapper class. 第二个是扩展方法依赖于静态Mapper类。 If that's the way you use AutoMapper then there is no problem. 如果这是您使用AutoMapper的方式,那么没有问题。 If you have multiple mapping engines and/or write code against interfaces of AutoMapper you can't use this extension method. 如果您有多个映射引擎和/或针对AutoMapper的接口编写代码,则无法使用此扩展方法。 To support both cases we need to add two optional arguments: IConfigurationProvider and IMappingEngine . 为了支持这两种情况,我们需要添加两个可选参数: IConfigurationProviderIMappingEngine

I try to avoid the use of the static Mapper class and inject the interfaces where I need them through an IoC container. 我尽量避免使用静态Mapper类,并通过IoC容器注入我需要的接口。

The third is that the extension method doesn't return the IMappingExpression<TSource, TDestination> and prohibits overriding the base member mappings. 第三个是扩展方法不返回IMappingExpression<TSource, TDestination>并禁止覆盖基本成员映射。 To fix this we return the IMappingExpression<TSource, TDestination> and remove the conditions on all members. 为了解决这个问题,我们返回IMappingExpression<TSource, TDestination>并删除所有成员的条件。

This results in the following code: 这导致以下代码:

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

Usage 用法

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

Maybe there are still some scenario's which aren't covered but it certainly fixes your problem and this way you don't need to write specific extension methods. 也许还有一些场景没有被覆盖,但它确实解决了你的问题,这样你就不需要编写特定的扩展方法了。

I ended up creating an extension method in the way described here and here . 我最终以这里这里描述的方式创建了一个扩展方法。

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

I'm still not sure why this line doesn't allow for multiple types to be specified inside a IDictionary<type, IEnumerable<type>> though. 我仍然不确定为什么这行不允许在IDictionary<type, IEnumerable<type>>指定多个类型。 I'm sure the folks at AutoMapper have their reasons. 我确信AutoMapper的人都有他们的理由。

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

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