将多个源属性映射到单个目标属性

时间:2015-04-20 12:05:01

标签: c# automapper

我想知道是否可以使用某种自定义类型或值解析器来处理这种情况。

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来实例化此对象。

当然,我想要做的是做一些通用的东西,所以不要想到MapFrom每个CreateMap中的Entity

我尝试使用自定义TypeConverter,但它只支持SourceType - > DestinationType 在我的情况下,我有stringDateTimeOffset,必须创建SuperDateTime

4 个答案:

答案 0 :(得分:2)

除了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被称为bla,则您的相应实体string称为blaZone,而您的模型SuperDateTime称为bla。

答案 1 :(得分:1)

您可以使用客户解析器。我使用自定义解析器来获取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相关的所有代码。 您可以进一步了解自定义解析器here

答案 2 :(得分:0)

对您的简短回答是'否',无法使用自定义值解析器来映射&lt; string,DateTimeOffset&gt; =&GT; 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的实例化以匹配提供的源代码。

答案 3 :(得分:0)

在睡觉之后,这是一种感觉更通用的替代方案。

假设你想做这样的事情:

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>的唯一问题是,您无法访问当前正在配置的成员。为简洁起见,我完全从memberOptions签名中删除了ForMemberType参数,但如果您需要,可以很容易地将其添加回来(也就是设置一些其他选项 - 请参阅示例{{3} })。

因此,为了能够编写上述内容,您需要的是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);
}