Из AutoMapper CodePlex веб — страницы, мы можем видеть , что 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()
{
}
}
Первый шаг в объектно-объекте Mapper является регистром в отношениях между оригиналом и целевыми объектами.
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));
}
}
Чтобы выполнить задачу, класс содержит mappingTypesDictionary, который хранит отношения между исходным и целевым типами. Через универсальный метод 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();
Затем он проходит через них. Если свойство с тем же именем отсутствует в целевом объекте, оно пропускается. Если есть и это не наш пользовательский класс (это системный класс like-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 .
Существует оптимизация, если значения внутренних типов свойств уже рассчитаны. Когда вычисляется зарегистрированный тип назначения, его значение помещается в коллекцию ужеInitializedObjects, и метод 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)
{
DateTime startTime = DateTime.Now;
List<FirstObject> firstObjects = new List<FirstObject>();
List<MapFirstObject> mapFirstObjects = new List<MapFirstObject>();
// REDUCED AUTO MAPPER TEST ------------------------------------------------------------
ReducedAutoMapper.Instance.CreateMap<FirstObject, MapFirstObject>();
ReducedAutoMapper.Instance.CreateMap<SecondObject, MapSecondObject>();
ReducedAutoMapper.Instance.CreateMap<ThirdObject, MapThirdObject>();
for (int i = 0; i < 100000; 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);
System.Console.WriteLine("Object created: {0}", i);
}
for (int i = 0; i < firstObjects.Count - 1; i++)
{
MapFirstObject mapSecObj = ReducedAutoMapper.Instance.Map<FirstObject, MapFirstObject>(firstObjects[i]);
mapFirstObjects.Add(mapSecObj);
System.Console.WriteLine("Map Object: {0}", i);
}
DateTime endTime = DateTime.Now;
System.Console.WriteLine("Finish for {0}", endTime - startTime);
System.Console.WriteLine();
}
}
Полученные результаты
Код ниже тестирует AutoMapper с 100000 объектов.
public class Program
{
static void Main(string[] args)
{
DateTime startTime = DateTime.Now;
List<FirstObject> firstObjects = new List<FirstObject>();
List<MapFirstObject> mapFirstObjects = new List<MapFirstObject>();
// AUTO MAPPER TEST ------------------------------------------------------------
AutoMapper.Mapper.CreateMap<FirstObject, MapFirstObject>();
AutoMapper.Mapper.CreateMap<SecondObject, MapSecondObject>();
AutoMapper.Mapper.CreateMap<ThirdObject, MapThirdObject>();
for (int i = 0; i < 1000; 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);
System.Console.WriteLine("Object created: {0}", i);
}
for (int i = 0; i < firstObjects.Count; i++)
{
MapFirstObject mapSecObj = AutoMapper.Mapper.Map<FirstObject, MapFirstObject>(firstObjects[i]);
mapFirstObjects.Add(mapSecObj);
System.Console.WriteLine("Map Object: {0}", i);
}
DateTime endTime = DateTime.Now;
System.Console.WriteLine("Finish for {0}", endTime - startTime);
System.Console.WriteLine();
}
}
Полученные результаты
Как вы можете видеть из приведенных выше результатов, ReducedAutoMapper производится 82% лучше , чем AutoMapper .
Вы можете скачать полный исходный код из моего репозитория GIT HUB — https://github.com/angelovstanton/Projects/tree/master/AAngelov.Utilities/AAngelov.Utilities.Test