AutoMapper自动映射对象减少180%
介绍
从AutoMapper CodePlex
Web页面,我们可以看到这AutoMapper
是一个对象对象映射器。对象对象映射通过将一种类型的输入对象转换为不同类型的输出对象来起作用。它有大量的设置,有时很难设置。在我的项目中,我需要自动映射没有集合属性的简单对象,只有自定义属性类型的大树
- TestCase
具有类型属性等的对象TestStep
。另外,有些罕见的情况AutoMapper
还不行。所以,我创建了 ReducedAutoMapper
只有150行代码,但它运行速度比80%快 AutoMapper.
减少AutoMapper说明
对象映射器的主要目标是将对象A映射到对象B.
原始对象类型 - 不可序列化
public class FirstObject
{
public FirstObject()
{
}
public string FirstName { get; set; }
public string SecondName { get; set; }
public string PoNumber { get; set; }
public decimal Price { get; set; }
public DateTime SkipDateTime { get; set; }
public SecondObject SecondObjectEntity { get; set; }
public List<SecondObject> SecondObjects { get; set; }
public List<int> IntCollection { get; set; }
public int[] IntArr { get; set; }
public SecondObject[] SecondObjectArr { get; set; }
}
目标对象 - 可序列化(相同的属性,仅添加序列化属性)
[DataContract]
public class MapFirstObject
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string SecondName { get; set; }
[DataMember]
public string PoNumber { get; set; }
[DataMember]
public decimal Price { get; set; }
[DataMember]
public MapSecondObject SecondObjectEntity { get; set; }
public MapFirstObject()
{
}
}
在对象物体映射器的第一步是要注册的关系原始和目标对象之间。
private Dictionary<object, object> mappingTypes;
public Dictionary<object, object> MappingTypes
{
get
{
return this.mappingTypes;
}
set
{
this.mappingTypes = value;
}
}
public void CreateMap<TSource, TDestination>()
where TSource : new()
where TDestination : new()
{
if (!this.MappingTypes.ContainsKey(typeof(TSource)))
{
this.MappingTypes.Add(typeof(TSource), typeof(TDestination));
}
}
要完成任务,该类包含mappingTypes Dictionary
哪些存储原始类型和目标类型之间的关系。通过通用方法 CreateMap
将类型添加到字典中。
样品注册
ReducedAutoMapper.Instance.CreateMap<FirstObject, MapFirstObject>();
主要AutoMapping算法如何运作?
在其核心,ReducedAutoMapper
大量使用Reflection来获取与自动映射对象相关的信息。
public TDestination Map<TSource, TDestination>(
TSource realObject,
TDestination dtoObject = default (TDestination),
Dictionary<object, object> alreadyInitializedObjects = null,
bool shouldMapInnerEntities = true)
where TSource : class, new()
where TDestination : class, new()
{
if (realObject == null)
{
return null;
}
if (alreadyInitializedObjects == null)
{
alreadyInitializedObjects = new Dictionary<object, object>();
}
if (dtoObject == null)
{
dtoObject = new TDestination();
}
var realObjectType = realObject.GetType();
PropertyInfo[] properties = realObjectType.GetProperties();
foreach (PropertyInfo currentRealProperty in properties)
{
PropertyInfo currentDtoProperty = dtoObject.GetType().GetProperty(currentRealProperty.Name);
if (currentDtoProperty == null)
{
////Debug.WriteLine("The property {0} was not found
////in the DTO object in order to be mapped.
/// Because of that we skip to map it.", currentRealProperty.Name);
}
else
{
if (this.MappingTypes.ContainsKey
(currentRealProperty.PropertyType) && shouldMapInnerEntities)
{
object mapToObject = this.mappingTypes[currentRealProperty.PropertyType];
var types = new Type[] { currentRealProperty.PropertyType, (Type)mapToObject };
MethodInfo method = GetType().GetMethod("Map").MakeGenericMethod(types);
var realObjectPropertyValue = currentRealProperty.GetValue(realObject, null);
var objects = new object[]
{
realObjectPropertyValue,
null,
alreadyInitializedObjects,
shouldMapInnerEntities
};
if (objects != null && realObjectPropertyValue != null)
{
if (alreadyInitializedObjects.ContainsKey
(realObjectPropertyValue) && currentDtoProperty.CanWrite)
{
// Set the cached version of the same object (optimization)
currentDtoProperty.SetValue(dtoObject, alreadyInitializedObjects
[realObjectPropertyValue]);
}
else
{
// Add the object to cached objects collection.
alreadyInitializedObjects.Add(realObjectPropertyValue, null);
// Recursively call Map method again to get the new proxy object.
var newProxyProperty = method.Invoke(this, objects);
if (currentDtoProperty.CanWrite)
{
currentDtoProperty.SetValue(dtoObject, newProxyProperty);
}
if (alreadyInitializedObjects.ContainsKey(realObjectPropertyValue)
&& alreadyInitializedObjects[realObjectPropertyValue] == null)
{
alreadyInitializedObjects[realObjectPropertyValue] = newProxyProperty;
}
}
}
else if (realObjectPropertyValue == null && currentDtoProperty.CanWrite)
{
// If the original value of the object was null set null to the destination property.
currentDtoProperty.SetValue(dtoObject, null);
}
}
else if (!this.MappingTypes.ContainsKey(currentRealProperty.PropertyType))
{
// If the property is not custom type just set normally the value.
if (currentDtoProperty.CanWrite)
{
currentDtoProperty.SetValue
(dtoObject, currentRealProperty.GetValue(realObject, null));
}
}
}
}
return dtoObject;
}
首先,它获取源对象的属性。
var realObjectType = realObject.GetType();
PropertyInfo[] properties = realObjectType.GetProperties();
接下来,它会遍历它们。如果目标对象中不存在具有相同名称的属性,则会跳过该属性。如果有,它不是我们的自定义类(它是一个System
类像-
,string
,int
),DateTime
将其值设置为原始的属性之一。
else if (!this.MappingTypes.ContainsKey(currentRealProperty.PropertyType))
{
// If the property is not custom type just set normally the value.
if (currentDtoProperty.CanWrite)
{
currentDtoProperty.SetValue(dtoObject, currentRealProperty.GetValue(realObject, null));
}
}
如果属性的类型是自定义类型,并且它不存在于字典中,则不会自动映射。
否则,对于要计算的目标对象的新值,我们使用反射来递归地调用通用的Map方法。
如果已经计算了内部属性类型的值,则会进行优化。当注册的目的地类型被计算时,它的值被放置在alreadyInitializedObjects
集合中,并且该方法Map
之后不被递归地调用。
如果你需要的对象的自动映射集合,你可以使用的第三种方法ReducedAutoMapper
类- MapList
。
public List<TDestination> MapList<TSource, TDestination>
(List<TSource> realObjects, Dictionary<object, object> alreadyInitializedObjects = null)
where TSource : class, new()
where TDestination : class, new()
{
List<TDestination> mappedEntities = new List<TDestination>();
foreach (var currentRealObject in realObjects)
{
TDestination currentMappedItem = this.Map<TSource,
TDestination>(currentRealObject, alreadyInitializedObjects: alreadyInitializedObjects);
mappedEntities.Add(currentMappedItem);
}
return mappedEntities;
}
将AutoMapper与ReducedAutoMapper进行比较
我创建了一个简单的控制台应用程序,其中我初始化了超过1000个属性的巨大对象。创建的对象的数量是100000。
以上,您可以找到第一个源类FirstObject
。下面你可以找到另外两个。
SecondObject
public class SecondObject
{
public SecondObject(string firstNameS, string secondNameS, string poNumberS, decimal priceS)
{
this.FirstNameS = firstNameS;
this.SecondNameS = secondNameS;
this.PoNumberS = poNumberS;
this.PriceS = priceS;
ThirdObject1 = new ThirdObject();
ThirdObject2 = new ThirdObject();
ThirdObject3 = new ThirdObject();
ThirdObject4 = new ThirdObject();
ThirdObject5 = new ThirdObject();
ThirdObject6 = new ThirdObject();
}
public SecondObject()
{
}
public string FirstNameS { get; set; }
public string SecondNameS { get; set; }
public string PoNumberS { get; set; }
public decimal PriceS { get; set; }
public ThirdObject ThirdObject1 { get; set; }
public ThirdObject ThirdObject2 { get; set; }
public ThirdObject ThirdObject3 { get; set; }
public ThirdObject ThirdObject4 { get; set; }
public ThirdObject ThirdObject5 { get; set; }
public ThirdObject ThirdObject6 { get; set; }
}
ThirdObject
{
public ThirdObject()
{
}
public DateTime DateTime1 { get; set; }
public DateTime DateTime2 { get; set; }
public DateTime DateTime3 { get; set; }
//.. it contains 996 properties more
public DateTime DateTime1000 { get; set; }
}
下面的代码ReducedAutoMapper
用100000
对象进行测试。
public class Program
{
static void Main(string[] args)
{
Profile("Test Reduced AutoMapper 10 Runs 10k Objects", 10, () => MapObjectsReduceAutoMapper());
System.Console.ReadLine();
}
static void Profile(string description, int iterations, Action actionToProfile)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < iterations; i++)
{
actionToProfile();
}
watch.Stop();
System.Console.WriteLine(description);
System.Console.WriteLine("Total: {0:0.00} ms ({1:N0} ticks) (over {2:N0} iterations)",
watch.ElapsedMilliseconds, watch.ElapsedTicks, iterations);
var avgElapsedMillisecondsPerRun = watch.ElapsedMilliseconds / iterations;
var avgElapsedTicksPerRun = watch.ElapsedMilliseconds / iterations;
System.Console.WriteLine("AVG: {0:0.00} ms ({1:N0} ticks) (over {2:N0} iterations)",
avgElapsedMillisecondsPerRun, avgElapsedTicksPerRun, iterations);
}
static void MapObjectsReduceAutoMapper()
{
List<FirstObject> firstObjects = new List<FirstObject>();
List<MapFirstObject> mapFirstObjects = new List<MapFirstObject>();
ReducedAutoMapper.Instance.CreateMap<FirstObject, MapFirstObject>();
ReducedAutoMapper.Instance.CreateMap<SecondObject, MapSecondObject>();
ReducedAutoMapper.Instance.CreateMap<ThirdObject, MapThirdObject>();
for (int i = 0; i < 10000; i++)
{
FirstObject firstObject =
new FirstObject(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), (decimal)12.2, DateTime.Now,
new SecondObject(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), (decimal)11.2));
firstObjects.Add(firstObject);
}
foreach (var currentObject in firstObjects)
{
MapFirstObject mapSecObj = ReducedAutoMapper.Instance.Map<FirstObject, MapFirstObject>(currentObject);
mapFirstObjects.Add(mapSecObj);
}
}
}
结果
下面的代码AutoMapper
用100000
对象进行测试。
public class Program
{
static void Main(string[] args)
{
Profile("Test Original AutoMapper 10 Runs 10k Objects", 10, () => MapObjectsAutoMapper());
System.Console.ReadLine();
}
static void Profile(string description, int iterations, Action actionToProfile)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < iterations; i++)
{
actionToProfile();
}
watch.Stop();
System.Console.WriteLine(description);
System.Console.WriteLine("Total: {0:0.00} ms ({1:N0} ticks) (over {2:N0} iterations)",
watch.ElapsedMilliseconds, watch.ElapsedTicks, iterations);
var avgElapsedMillisecondsPerRun = watch.ElapsedMilliseconds / iterations;
var avgElapsedTicksPerRun = watch.ElapsedMilliseconds / iterations;
System.Console.WriteLine("AVG: {0:0.00} ms ({1:N0} ticks) (over {2:N0} iterations)",
avgElapsedMillisecondsPerRun, avgElapsedTicksPerRun, iterations);
}
static void MapObjectsAutoMapper()
{
List<FirstObject> firstObjects = new List<FirstObject>();
List<MapFirstObject> mapFirstObjects = new List<MapFirstObject>();
AutoMapper.Mapper.CreateMap<FirstObject, MapFirstObject>();
AutoMapper.Mapper.CreateMap<SecondObject, MapSecondObject>();
AutoMapper.Mapper.CreateMap<ThirdObject, MapThirdObject>();
for (int i = 0; i < 10000; i++)
{
FirstObject firstObject =
new FirstObject(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), (decimal)12.2, DateTime.Now,
new SecondObject(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), (decimal)11.2));
firstObjects.Add(firstObject);
}
foreach (var currentObject in firstObjects)
{
MapFirstObject mapSecObj = AutoMapper.Mapper.Map<FirstObject, MapFirstObject>(currentObject);
mapFirstObjects.Add(mapSecObj);
}
}
}
结果
从上面的结果可以看出, ReducedAutoMapper 比AutoMapper 执行的> 180 %。
在C#系列中呢
1. 实现复制粘贴C#代码
2. MSBuild TCP IP日志记录器C#代码
3. Windows注册表读写入C#代码
4. 在运行时更改.config文件C#代码
5. 通用属性验证器C#代码
6. 减少AutoMapper自动映射对象180 %更快
7. 7 C#6.0中的新酷特性
8. 代码覆盖范例 - C#
9中的 示例MSTest通过MSTest.exe Wrapper应用程序重新运行失败的测试
10. 有效地在Visual Studio中安排应用程序的提示
11. 19必须知道的视觉工作室键盘快捷键 - 第1部分
12。 19必须知道的Visual Studio键盘快捷键 - 第2部分
13. 根据Visual Studio
14中的构建配置指定装配引用 14. .NET 15的未充分利用的特性
15. .NET第2部分的十五个未充分利用的特性
16. 轻松格式化的整洁技巧C#中的货币
17.声明 日期时间正确的方式MSTest NUnit C#代码
18. 哪个工作更快 - 空聚合运算符或GetValueOrDefault或条件运算符
19. 基于规范的测试设计技术来增强单元测试
20. 使用Lambda表达式获取属性名称在C#
21. 使用C#的前9个Windows事件日志提示