我有两种类型:
class Source {
public object Data { get; set; }
}
class Destination {
public object Data { get; set; }
}
我希望在Source
和Destination
中的位置之间进行映射,属性Data
是存在映射的某种类型。例如。
Mapper.CreateMap<SourceData1, DestinationData1>();
Mapper.CreateMap<SourceData2, DestinationData2>();
目前我的映射文件如下:
Mapper.CreateMap<Source, Destination>();
Mapper.CreateMap<SourceData1, DestinationData1>();
Mapper.CreateMap<SourceData2, DestinationData2>();
// This is the bit that looks bad to me:
Mapper
.CreateMap<object, object>()
.Include<SourceData1, DestinationData1>()
.Include<SourceData2, DestinationData2>()
需要包含来自对象的所有允许的地图似乎有点笨拙,并且一旦解决方案有所增长,可能会变得非常烦人。如果我不包含它们,则Map
无法正确映射这些类型。
有没有办法解决这个问题?
答案 0 :(得分:0)
我意识到我希望从映射器中获取的行为是基于Source.Data
的运行时类型将Destination.Data
注入Source.Data
。具体来说,对于给定类型TSource
,我希望能够准确定义一个目标类型TDestination
,这样当Source.Data
具有类型TSource
时,Destination.Data
具有类型TDestination
。
Map
或DynamicMap
的问题是您在执行映射时必须知道目标类型,但在配置时,您对Destination.Data
的类型的所有了解都是它是object
- 您需要显式声明类型目标类型,并且唯一可以做到这一点的方法是保持源类型到目标类型的映射。
我在AutoMapper
配置方法上实现了几个扩展方法,允许注册Map
进行注入,并使用注入策略解析Member
:< / p>
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using AutoMapper;
/// <summary>
/// The <seealso cref="AutoMapper"/> extensions.
/// </summary>
public static class AutoMapperExtensions
{
/// <summary>
/// The injection maps.
/// </summary>
private static readonly SortedDictionary<Type, Type> Injections =
new SortedDictionary<Type, Type>(new TypeComparer());
/// <summary>
/// Registers a mapping expression for injection.
/// </summary>
/// <param name="expression">
/// The expression to register fo injection.
/// </param>
/// <typeparam name="TSource">
/// The source type.
/// </typeparam>
/// <typeparam name="TDestination">
/// The destination type.
/// </typeparam>
/// <returns>
/// The original <see cref="IMappingExpression"/> to allow for fluent chaining.
/// </returns>
/// <exception cref="InvalidOperationException">
/// Thrown when <typeparam name="TSource"/> is an interface or has already been enabled.
/// </exception>
public static IMappingExpression<TSource, TDestination> RegisterForInjection<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> expression)
where TSource : class
where TDestination : class // These constraints mean that null can be mapped to null.
{
var sourceType = typeof(TSource);
// Interfaces do not have a strict hierarchy so the cannot be registered.
if (sourceType.IsInterface)
{
throw new InvalidOperationException(
string.Format(
"The type {0} is an interface and interface types cannot be registered for injection",
sourceType));
}
// This is important: for injection to work there can be exactly one target.
if (Injections.ContainsKey(sourceType))
{
throw new InvalidOperationException(
string.Format("The type {0} has already been registered for injection", sourceType));
}
Injections.Add(sourceType, typeof(TDestination));
return expression;
}
/// <summary>
/// Instructs the mapper to resolve the destination member using an injection
/// strategy based on the actual type of the source member at runtime.
/// </summary>
/// <param name="expression">
/// The member expression on which to use an injection strategy.
/// </param>
/// <param name="sourceMember">
/// The source member to map from.
/// </param>
/// <typeparam name="TSource">
/// The source type.
/// </typeparam>
/// <typeparam name="TMember">
/// The source member type (probably object).
/// </typeparam>
/// <exception cref="InvalidOperationException">
/// When the actual type of <typeparam name="TMember"/> has not been registered for injection.
/// </exception>
public static void InjectFrom<TSource, TMember>(
this IMemberConfigurationExpression<TSource> expression,
Expression<Func<TSource, TMember>> sourceMember)
where TMember : class
{
var getSourceMember = sourceMember.Compile();
expression.ResolveUsing(
s =>
{
var sourceMemberValue = getSourceMember(s);
if (sourceMemberValue == null)
{
return null;
}
var sourceMemberType = sourceMemberValue.GetType();
Type destinationMemberType = null;
// Because the injections are sorted by number of super classes it is
// guaranteed that the first one that is assignable from the source type
// is the most derived.
foreach (var injection in Injections)
{
if (injection.Key.IsAssignableFrom(sourceMemberType))
{
// {injection.Key} value = default({sourceMemberType}) compiles.
destinationMemberType = injection.Value;
break;
}
}
if (destinationMemberType == null)
{
throw new InvalidOperationException(
string.Format(
"The type {0} has not been enabled for injection.",
sourceMemberType));
}
return Mapper.Map(sourceMemberValue, sourceMemberType, destinationMemberType);
});
}
/// <summary>
/// The type comparer.
/// </summary>
private class TypeComparer : IComparer<Type>
{
/// <summary>
/// Compares <paramref name="x"/> with <paramref name="y"/>.
/// </summary>
/// <param name="x">
/// The left hand type.
/// </param>
/// <param name="y">
/// The right hand type
/// </param>
/// <returns>
/// The difference in the number of super classes between y and x, specifically:
/// x < y if x has more super classes than y
/// </returns>
public int Compare(Type x, Type y)
{
return CountSuperClasses(y) - CountSuperClasses(x);
}
/// <summary>
/// Counts the number of super classes beneath <paramref name="type"/>.
/// </summary>
/// <param name="type">
/// The type.
/// </param>
/// <returns>
/// The number of super classes beneath <paramref name="type"/>.
/// </returns>
private static int CountSuperClasses(Type type)
{
var count = 0;
while (type.BaseType != null)
{
++count;
type = type.BaseType;
}
return count;
}
}
}