Alex
Alex

Reputation: 2144

Best Practices for mapping one object to another

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!

Upvotes: 53

Views: 204006

Answers (7)

Alex from Jitbit
Alex from Jitbit

Reputation: 60566

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)

Upvotes: 9

Ali Ahmadi
Ali Ahmadi

Reputation: 670

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

Upvotes: 0

Celso Lívero
Celso Lívero

Reputation: 726

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>();

Upvotes: 3

Amjad Abujamous
Amjad Abujamous

Reputation: 920

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.

Upvotes: 7

DKM
DKM

Reputation: 61

/// <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);
       }
   }
}

Upvotes: 1

Stefano Altieri
Stefano Altieri

Reputation: 4628

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.

Upvotes: 10

Efran Cobisi
Efran Cobisi

Reputation: 6454

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.

Upvotes: 34

Related Questions