AutoMapper-对象自动映射
在企业应用开发中,很多时候都需要将一个对象转换为另一个对象,比如说在WCF应用的开发中,需要将一个Entity转换为一个Contract对象。大部分情况下,这两个对象会非常相似,有个相同的属性名和类型。如果每次我们都要手写这中转换代码,不但容易出错,而且工作量也非常可观。所以开发一个自动映射类还是非常必要的。我们把此类命名为AutoMapper,它应该实现以下功能:
如果分属两个类的两个属性在满足某一类条件时可以映射:
-
集合映射,所有实现了IEnumerable<T>的类型都作为集合类映射。集合映射又包括以下两种映射:
-
基本集合映射,集合内元素类型相同。
-
Twin集合映射,集合内元素类型不同,而且都为引用类型但不是string。
-
-
单值映射,非集合映射的属性,使用单值映射。单值映射也包括以下两种映射。
-
基本类型映射,属性类型相同者,使用此映射。
-
Twin映射,属性类型不同且都为引用类型但不是string类型,使用此类映射。
-
对于以上映射,映射成立,必须满足一下条件:
-
属性名相同
-
源属性必须可读。
-
如果目标属性是值类型或者string类型,那么目标属性必须可写。
-
如果目标属性是引用类型,那么目标属性可读,而且可以得到一个非空值,否则目标属性必须可写。
AutoMapper类型还应该提供配置功能,调用这可以为某个映射配置,配置包括忽略某个属性映射,即使该属性满足映射条件。将两个不同名的属性配置为可以映射,属性类型必须满足映射条件,否则运行时,该属性还是不能映射。关于映射配置,将在后面讲述。
AutoMapper类应该有如下方法定义:
public static class AutoMapper
{
/// <summary>
/// 将一个集合转换为另一个集合,两种类型必须是可转换的.
/// </summary>
public static IEnumerable<TTarget>MapTo<TSource, TTarget>(this IEnumerable<TSource> sourceItems)
where TSource : class
where TTarget : class, new();
/// <summary>
/// 将源对象转换为目标类型的对象。
/// </summary>
public static TTarget MapTo<TSource,TTarget>(this TSource source)
where TSource : class
where TTarget : class, new();
/// <summary>
/// 将源对象转换为目标类型的对象。目标对象需要调用者事先创建出来
/// </summary>
public static TTarget MapTo<TSource,TTarget>(this TSource source, TTarget target)
where TSource : class
where TTarget : class;
}
AutoMapper类依赖于TypeMap<TSource,TTarget>类型来映射指定了源和目标类型的映射。TypeMap<TSource,TTarget>类型将会根据两个类型来建立一个PropertyMap对象的集合,PropertyMap对象负责单个属性的映射。从PropertyMap的继承关系如下:
interface IpropertyMap
abstract classPropertyMap : IpropertyMap
class BasicPropertyMap : PropertyMap
class TwinPropertyMap : PropertyMap
abstract class EnumerablePropertyMap :PropertyMap
classBasicEnumerablePropertyMap : EnumerablePropertyMap
classTwinEnumerablePropertyMap : EnumerablePropertyMap
继承层次的叶子类跟前面讲述的集中映射类型一一对应。
为了提高映射性能,使用了OpenDelegate。关于开放委托,网上有详细介绍。
提供了MapConfiguration<TSource,TTarget>类来完成配置映射的任务。该类具有以下两个方法来完成映射配置。
public class MapConfiguration<TSource, TTarget> : MapConfiguration
where TSource : class
where TTarget : class
{
public MapConfiguration<TSource, TTarget>Ignore<TProperty>(Expression<Func<TSource, TProperty>> propExp);
public MapConfiguration<TSource, TTarget>MapName<TProperty>(
Expression<Func<TSource, TProperty>> sourcePropExp,
Expression<Func<TTarget, TProperty>> targetPropExp);
public bool IsIgnored(string propName);
public bool TryGetMappedProperty(string sourceProp, out string targetProp);
}
下面是几个使用AutoMapper类的例子:
User u = new User()
{
Bool = true,
Byte = 1,
Char = 'f',
DateTime = DateTime.Now
};
var c = u.MapTo<User, UserContract>();
Assert.AreEqual(u.Bool, c.Bool);
Assert.AreEqual(u.Byte, c.Byte);
Assert.AreEqual(u.Char, c.Char);
Assert.AreEqual(u.DateTime,c.DateTime);
节省的代码是显而易见的,而且性能也不差。
下面是使用配置的例子,一定要在调用MapTo之前配置好映射关系,否则之后配置的映射关系是不起作用的。
[ClassInitialize()]
public static void MyClassInitialize(TestContext testContext)
{
AutoMapper.Configure<User, UserContract>()
.MapName(user =>user.AccountName, uc => uc.UserName)
.Ignore(user => user.Byte);
}
User u = new User()
{
Bool = true,
Byte = 1,
};
var c = u.MapTo<User, UserContract>();
Assert.AreEqual(u.Byte, 1);
Assert.AreEqual(c.Byte, 0);
为不同名属性指定映射:
User u = new User()
{
AccountName = "mac"
};
var c = u.MapTo<User, UserContract>();
Assert.AreEqual(u.AccountName,c.UserName);
下面是代码部分:
该帮助类用来封装一下反射代码:
internal static class ReflectionUtils
{
//在运行时得到一个实现了IEnumerable<T>类型的泛型参数的类型。
public static Type GetElementTypeOfIEnumerable(Type type)
{
if (type.IsArray)
{
return type.GetElementType();
}
if (type.IsInterface)
{
Type def =type.GetGenericTypeDefinition();
if (def == typeof(IEnumerable<>))
{
returntype.GetGenericArguments()[0];
}
}
else
{
Type interfaceType = type
.GetInterfaces()
.FirstOrDefault(t =>t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>));
if (interfaceType != null)
{
returninterfaceType.GetGenericArguments()[0];
}
}
return null;
}
//判断一个类型是引用类型但不是string类型。
public static bool IsReferenceTypeButString(Type type)
{
return !type.IsValueType &&type != typeof(string);
}
//判断一个类型是值类型或者是string类型。
public static bool IsValueTypeOrString(Type type)
{
return type.IsValueType || type == typeof(string);
}
//创建一个集合,如果是数组,就会使用count参数创建指定长度的数组
public static IEnumerable<object> CreateCollection(Type collType, int count)
{
object instance = null;
var elementType = ReflectionUtils.GetElementTypeOfIEnumerable(collType);
if (collType.IsArray)
{
instance = Array.CreateInstance(elementType,count);
}
else if (collType.IsInterface)
{
var listType = typeof(List<>).MakeGenericType(elementType);
if(collType.IsAssignableFrom(listType))
{
instance = Activator.CreateInstance(listType);
}
}
else
{
instance =CreateInstance(collType);
}
return instance as IEnumerable<object>;
}
//将sources中的对象拷贝到目标集合中。Target必须是一个数组或者实现了Ilist接口。
public static void CopyElements(object[] source, object target)
{
if(source =