簡體   English   中英

Automapper映射具有繼承性的基類

[英]Automapper Mapping base classes with inheritence

我在使用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;
}

顯然,這意味着您只能為每個包含的源類型指定一個目標。 但是,我沒有看到任何東西,這會阻止它們支持多個目標類型。

您可以查看此自定義擴展方法 源代碼也可以在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)
    ;

也許還有一些場景沒有被覆蓋,但它確實解決了你的問題,這樣你就不需要編寫特定的擴展方法了。

我最終以這里這里描述的方式創建了一個擴展方法。

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

我仍然不確定為什么這行不允許在IDictionary<type, IEnumerable<type>>指定多個類型。 我確信AutoMapper的人都有他們的理由。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM