簡體   English   中英

將多個源屬性映射到單個目標屬性

[英]Mapping multiple source properties to a single destination property

我想知道是否有辦法用一些自定義類型或值解析器來處理這種情況。

public class SuperDateTime
{
    public DateTimeOffset Date { get; set; }

    public string Timezone { get; set; }
}

public class Entity 
{
    public DateTimeOffset CreationDate { get; set; }

    public string CreationDateZone { get; set; }

    public DateTimeOffset EndDate { get; set; }

    public string EndDateZone { get; set; }
}

public class Model
{
    public SuperDateTime CreationDate { get; set; }

    public SuperDateTime EndDate { get; set; }
}

當我在目標對象中有SuperDateTime時,我想使用關聯的DateTimeOffset和源對象中的時區string來實例化此對象。

當然,我想做的是制作通用的東西,所以不要想到每個Entity每個CreateMap中的MapFrom

我嘗試使用自定義TypeConverter,但它只支持SourceType - > DestinationType在我的情況下,我有一個stringDateTimeOffset ,必須創建一個SuperDateTime

除了LiamK所建議的,下一個可能的改進是編寫一個輔助方法來做.MapFrom 根據您的要求,它可以是簡單的也可以是復雜的。 我將提供一個簡單的假設,但您可以修改和優化它以滿足您的可能需求。

static IMappingExpression<TFrom, TTo> MapSuperDateTime<TFrom, TTo>(
    this IMappingExpression<TFrom, TTo> expression, 
    Expression<Func<TTo, object>> dest)
{
    var datePropertyName = ReflectionHelper.FindProperty(dest).Name;
    var timezomePropertyName = datePropertyName + "Zone";
    var fromType = typeof (TFrom);
    var datePropertyGetter = fromType.GetProperty(datePropertyName).ToMemberGetter();
    var timezonePropertGetter = fromType.GetProperty(timezomePropertyName).ToMemberGetter();

    return expression.ForMember(dest, opt => opt.MapFrom(src => new SuperDateTime
    {
        Date = (DateTimeOffset)datePropertyGetter.GetValue(src),
        Timezone = (string)timezonePropertGetter.GetValue(src)         
    }));
}

然后你可以像這樣指定你的映射:

Mapper.CreateMap<Entity, Model>()
    .MapSuperDateTime(dest => dest.CreationDate)
    .MapSuperDateTime(dest => dest.EndDate);

假設是,如果你的實體DateTimeOffset被稱為喇嘛,那么你相應的實體string被稱為blaZone,和你的模型SuperDateTime被稱為喇嘛。

您可以使用客戶解析器。 我使用自定義解析器來獲取int這樣的對象;

讓我們說你正在創建這樣的映射(Althoug你沒有展示你是如何創建它的):

Mapper.CreateMap<YourSource, YourDestination>()
                .ForMember(x => x.DateTimeOffset, opt => opt.ResolveUsing(new DateTimeOffsetResolver(loadRepository)).FromMember(x => x.timezone));

這就是你的解析器的樣子:

public class DateTimeOffsetResolver : ValueResolver<string, DateTimeOffset>
    {
        private DatabaseLoadRepository loadRepository;
        public personIdResolver(DatabaseLoadRepository repo)
        {
            this.loadRepository = repo;
        }
        protected override DateTimeOffset ResolveCore(string timeZone)
        {
            //Your logic for converting string into dateTimeOffset goes here
            return DateTimeOffset; //return the DateTimeOffset instance
        }
    }

如果不需要訪問,可以刪除與Nhibernate Repository相關的所有代碼。 您可以在此處進一步了解自定義解析器

你問的簡答題是'不',沒有辦法使用自定義值解析器來映射<string,DateTimeOffset> => SuperDateTime並避免重復的.MapFrom調用。 在上面的示例中,這樣的值解析器無法區分映射期間哪些字符串和DateTimeOffsets在一起。

不確定您是否自己擁有.MapFrom代碼,但如果沒有,以下是您問題的最佳解決方案:

Mapper.CreateMap<Entity, Model>()
      .ForMember(
           dest => dest.CreationDate,
           opt => opt.MapFrom(
               src => new SuperDateTime()
                     {
                           Date = src.CreationDate, 
                           TimeZone = src.CreationDateZone
                     };
            ));

如果你真的想避免過多的MapFrom聲明,看看是否有辦法在這里利用映射繼承。

編輯:修改SuperDateTime的實例化以匹配提供的源代碼。

在睡覺之后,這里有一種感覺更通用的替代方案。

假設,你想做這樣的事情:

Mapper.CreateMap<Entity, Model>()
    .ForMemberType((member,src) => new SuperDateTime
            {
                Date = (DateTimeOffset)GetPropertyValue(src, member),
                Timezone = (string)GetPropertyValue(src, member+"Zone")
            });

這看起來比我的第一個答案好一些,因為在這里您指定要立即映射SuperDateTime所有成員。 (該類型是從lambda的返回類型推斷出來的。)實際上,類似於AutoMapper已經擁有的ForAllMembers 您無法使用標准memberOptions作為IMemberConfigurationExpression<TSource>的唯一問題是不允許您訪問當前正在配置的成員。 為簡潔起見,我完全從ForMemberType簽名中刪除了memberOptions參數,但如果需要,可以很容易地將其添加回來(也就是設置其他一些選項 - 請參見此處的示例)。

所以為了能夠編寫上面所有你需要的是GetPropertyValue方法,它看起來像這樣:

public object GetPropertyValue(object o, string memberName)
{
    return o.GetType().GetProperty(memberName).ToMemberGetter().GetValue(o);
}

ForMemberType方法本身,看起來像這樣:

public static IMappingExpression<TSource, TDestination> ForMemberType<TSource, TDestination, TMember>(
    this IMappingExpression<TSource, TDestination> expression,
    Func<string, TSource, TMember> memberMapping
    )
{
    return new TypeInfo(typeof(TDestination))
        .GetPublicReadAccessors()
        .Where(property => property.GetMemberType() == typeof(TMember))
        .Aggregate(expression, (current, property)
            => current.ForMember(property.Name, 
               opt => opt.MapFrom(src => memberMapping(property.Name, src))));
}

就是這樣。 為避免每次重新編譯屬性getter,您可能希望添加一個非常簡單的緩存層,為每種類型編譯一次(執行ToMemberGetter )並在某處記住結果。 使用AutoMapper自己的DictonaryFactory然后IDictionary.GetOrAdd可能是最直接的方式:

private readonly IDictionary<string, IMemberGetter> _getters 
    = new DictionaryFactory().CreateDictionary<string, IMemberGetter>();        
public object GetPropertyValue(object o, string memberName)
{
    var getter = _getters.GetOrAdd(memberName + o.GetType().FullName, x => o.GetType()
        .GetProperty(memberName).ToMemberGetter());
    return getter.GetValue(o);
}

暫無
暫無

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

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