Velvet Star Monitor

Standout celebrity highlights with iconic style.

news

Best Practices for mapping one object to another

Writer Andrew Henderson

My question is, what is the best way I can map one object to another in the most maintainable manner. I cannot change the way the Dto object that we are getting is setup to be more normalized so I need to create a way to map this to our implementation of their object.

Here is example code to show what I need to happen:

class Program
{ static void Main(string[] args) { var dto = new Dto(); dto.Items = new object[] { 1.00m, true, "Three" }; dto.ItemsNames = new[] { "One", "Two", "Three" }; var model = GetModel(dto); Console.WriteLine("One: {0}", model.One); Console.WriteLine("Two: {0}", model.Two); Console.WriteLine("Three: {0}", model.Three); Console.ReadLine(); } private static Model GetModel(Dto dto) { var result = new Model(); result.One = Convert.ToDecimal(dto.Items[Array.IndexOf(dto.ItemsNames, "One")]); result.Two = Convert.ToBoolean(dto.Items[Array.IndexOf(dto.ItemsNames, "Two")]); result.Three = dto.Items[Array.IndexOf(dto.ItemsNames, "Three")].ToString(); return result; }
}
class Dto
{ public object[] Items { get; set; } public string[] ItemsNames { get; set; }
}
class Model
{ public decimal One { get; set; } public bool Two { get; set; } public string Three { get; set; }
}

I think what would be great is if I had some sort of mapper class that would take in the model objects propertyInfo, the type I want to convert to, and the "itemname" I want to pull out. Does anyone have any suggestions to make this cleaner?

Thanks!

2

7 Answers

I would opt for AutoMapper, an open source and free mapping library which allows to map one type into another, based on conventions (i.e. map public properties with the same names and same/derived/convertible types, along with many other smart ones). Very easy to use, will let you achieve something like this:

Model model = Mapper.Map<Model>(dto);

Not sure about your specific requirements, but AutoMapper also supports custom value resolvers, which should help you writing a single, generic implementation of your particular mapper.

5

This is a possible generic implementation using a bit of reflection (pseudo-code, don't have VS now):

public class DtoMapper<DtoType>
{ Dictionary<string,PropertyInfo> properties; public DtoMapper() { // Cache property infos var t = typeof(DtoType); properties = t.GetProperties().ToDictionary(p => p.Name, p => p); } public DtoType Map(Dto dto) { var instance = Activator.CreateInstance(typeOf(DtoType)); foreach(var p in properties) { p.SetProperty( instance, Convert.Type( p.PropertyType, dto.Items[Array.IndexOf(dto.ItemsNames, p.Name)]); return instance; } }

Usage:

var mapper = new DtoMapper<Model>();
var modelInstance = mapper.Map(dto);

This will be slow when you create the mapper instance but much faster later.

1

Efran Cobisi's suggestion of using an Auto Mapper is a good one. I have used Auto Mapper for a while and it worked well, until I found the much faster alternative, Mapster.

Given a large list or IEnumerable, Mapster outperforms Auto Mapper. I found a benchmark somewhere that showed Mapster being 6 times as fast, but I could not find it again. You could look it up and then, if it is suits you, use Mapster.

/// <summary>
/// map properties
/// </summary>
/// <param name="sourceObj"></param>
/// <param name="targetObj"></param>
private void MapProp(object sourceObj, object targetObj)
{ Type T1 = sourceObj.GetType(); Type T2 = targetObj.GetType(); PropertyInfo[] sourceProprties = T1.GetProperties(BindingFlags.Instance | BindingFlags.Public); PropertyInfo[] targetProprties = T2.GetProperties(BindingFlags.Instance | BindingFlags.Public); foreach (var sourceProp in sourceProprties) { object osourceVal = sourceProp.GetValue(sourceObj, null); int entIndex = Array.IndexOf(targetProprties, sourceProp); if (entIndex >= 0) { var targetProp = targetProprties[entIndex]; targetProp.SetValue(targetObj, osourceVal); } }
}
1

Using reflection

 public interface IModelBase { int Id { get; set; } } public interface IDtoBase { int Id { get; set; } } public class Client : IModelBase { public int Id { get; set; } public string Name { get; set; } public ICollection<SomeType> ListOfSomeType { get; set; } } public class ClientDto : IDtoBase { public int Id { get; set; } public string Name { get; set; } } public static class Extensions { public static TDto AsDto<T, TDto>(this T item) where TDto : class, IDtoBase where T : class, IModelBase { var list = item.GetType().GetProperties(); var inst = Activator.CreateInstance(typeof(TDto)); foreach (var i in list) { if (((TDto)inst).GetType().GetProperty(i.Name) == null) continue; var valor = i.GetValue(item, null); ((TDto)inst).GetType().GetProperty(i.Name).SetValue((TDto)inst, valor); } return (TDto)inst; } }
How to use it: Client client = new { id = 1, Name = "Jay", ListOfSomeType = new List<SomeType>() }; ClientDto cdto = client.AsDto<Client, ClientDto>();

The fastest way to mapping two objects is inline-mapping, but maybe it took time so that you can use MappingGenerator

And also, you can see the benchmark from Jason Bock to compare, which is better below:

enter image description hereFull video on youtube

I created a generic method inspired from DKM's answer.

public static class DbHelper
{ public static T FillWith<T>(this T targetObj, T sourceObj) { Type T1 = sourceObj.GetType(); Type T2 = targetObj.GetType(); PropertyInfo[] sourceProprties = T1.GetProperties(BindingFlags.Instance | BindingFlags.Public); PropertyInfo[] targetProprties = T2.GetProperties(BindingFlags.Instance | BindingFlags.Public); foreach (var sourceProp in sourceProprties) { object osourceVal = sourceProp.GetValue(sourceObj, null); int entIndex = Array.IndexOf(targetProprties, sourceProp); if (entIndex >= 0) { var targetProp = targetProprties[entIndex]; targetProp.SetValue(targetObj, osourceVal); } } return targetObj; }
}

Usage:

var oldUser = new User();
oldUser.FillWith(updatedUser);

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy