51

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!

Alex
  • 2,114
  • 9
  • 25
  • 34
  • Not sure about the mapping, but you should definitely look at generics and using generic collections : http://csharp-station.com/Tutorial/CSharp/Lesson20 – Christian Phillips Apr 20 '13 at 08:17
  • 1
    I would suggest a costructor of Model which takes a Dto and maps/converts/checks accordingly hardcoded as you get compile errors when something changes in dto. Reflection and therefore dealing with strings does not help you to increase maintainability. –  Apr 20 '13 at 08:24
  • Well I don't know the best practice but if you are looking for `DTO` conversion have a look at my answer here : https://stackoverflow.com/a/76507857/3057246 It works as both Auto/Manual mapper using reflection. – Vinod Srivastav Jun 19 '23 at 15:12

7 Answers7

32

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.

Efran Cobisi
  • 6,138
  • 22
  • 22
  • 21
    We have used automapper before, but we have sense dropped it due to it's slow performance. – Alex Apr 22 '13 at 15:02
  • 8
    Agreed. Done the same thing, ended up with more work dropping automapper then writing a custom one in the first place. Automapper has very very slow performance – ZolaKt Jun 10 '15 at 12:27
  • 4
    We had pretty much the same issue with performance, won't be using it again. – James Z. Feb 12 '18 at 04:49
  • We use AutoMapper at my employer and yeah it's slow and it can a PITA to configure. – Justin Jul 26 '18 at 15:59
  • 3
    Try [Mapster](https://github.com/MapsterMapper/Mapster) instead for performance. YMMV but it says its much faster. – gbjbaanb Aug 05 '18 at 16:07
9

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.

PeteGO
  • 5,597
  • 3
  • 39
  • 70
Stefano Altieri
  • 4,550
  • 1
  • 24
  • 41
  • Unfortunately, the need here is not as straight forward as I would like it to and the item Names do not necessary correlate to the names of the properties on the model so I don't think this will work. – Alex Apr 22 '13 at 15:01
7

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.

Amjad Abujamous
  • 736
  • 9
  • 9
7

This question is very old and things have changed. Modern C# and .NET 6 come with a great feature called "source generators" - it generates C# code at compile time based on object's meta-data.

You literally get auto-generated code like this:

//auto-generated code at build time
objA.Property1 = objB.Property1;
objA.Property2 = objB.Property2;

It's like "ahead-of-time" reflection, but it works at build time, which makes it lightning fast.

So the most efficient way to map objects in 2023 is to use source generators. You can write your own or use a package like Riok.Mapperly https://github.com/riok/mapperly for example (I'm not affiliated, been a happy user for a while)

Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149
3

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>();
Celso Lívero
  • 716
  • 2
  • 14
  • 18
1
/// <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);
       }
   }
}
JNYRanger
  • 6,829
  • 12
  • 53
  • 81
DKM
  • 61
  • 1
  • 6
    -1 : this is a terrible way of doing a mapper, as it uses the order of the property to map them (instead of name, type, etc). – Lucca Ferri Sep 16 '19 at 13:51
0

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 here Full video on youtube

Ali Ahmadi
  • 563
  • 5
  • 9