我想知道是否可以使用某种自定义类型或值解析器来处理这种情况。
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
在我的情况下,我有string
和DateTimeOffset
,必须创建SuperDateTime
答案 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);
}