Converting between entities and data transfer objects


Share

If you’re on a path that embraces domain-driven design, you’ve likely touched on a fundamental concept that you’re trying very hard to smash the idea that you create a data model and then wire it to an object model through some flavor of object-relational mapping. Instead, you want to develop a domain that contains entities, or objects with behaviours, and, when necessary, map that domain to “data transfer objects” that are essentially copies of the domain entities without behavior, and perform your persistence.

One of the challenges is that you now own another abstraction in the entity to data transfer object mapping. You can use code generation tools to create concrete mapping, you can bend the rules a bit and create constructors in both the entity and data transfer object that act like copy constructors in either direction, and I’ve even seen some code that wraps the inner data transfer object inside the entity itself.

While continuing my own learning of this approach I decided to do this work dynamically using extension methods, to keep the entity and the data transfer object clearly separated. This approach probably suffers from performance implications in a more complex design, but you could improve its speed using dynamic methods and delegates if needed.

The usage is straightforward. Classes that implement IEntity can call ToDto<T>, and vice versa. The additional type is needed to work with the class directly after conversion without casting.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using EntityDto;
 
namespace EntityDto
{
    public static class EntityExtensions
    {
        private static readonly IDictionary<Type, IEnumerable<PropertyInfo>> _properties = 
            new Dictionary<Type, IEnumerable<PropertyInfo>>();
 
        public static T ToDto<T>(this IEntity entity)
            where T : IDataTransferObject
        {
            var instance = ConvertTo<IEntity, T>(entity);
            return instance;
        }
 
        public static K ToEntity<K>(this IDataTransferObject dto)
            where K : IEntity
        {
            var instance = ConvertTo<IDataTransferObject, K>(dto);
            return instance;
        }
 
        private static K ConvertTo<T, K>(T instance)
        {
            var other = Activator.CreateInstance<K>();
            var info = CachedType(typeof(K));
            foreach (var pair in instance.ToDictionary())
            {
                var entry = pair;
                var pi = info.SingleOrDefault(p => p.Name == entry.Key);
 
                if (pi != null && pi.CanWrite)
                {
                    pi.SetValue(other, pair.Value, null);
                }
            }
            return other;
        }
 
        private static IEnumerable<PropertyInfo> CachedType(Type type)
        {
            var flags = BindingFlags.Public | BindingFlags.Instance;
            if(!_properties.ContainsKey(type))
            {
                _properties.Add(type, type.GetProperties(flags));
            }
            return _properties[type];
        }
 
        private static IDictionary<string, object> ToDictionary(this object instance)
        {
            var info = CachedType(instance.GetType());
            return info
                .Select(n => n.Name)
                .ToDictionary(k => k, k =>
                                      {
                                          var value = info
                                              .Single(p => p.Name == k)
                                              .GetValue(instance, null);
                                          return value;
                                      });
        }
    }
}

Technorati Tags: ,,,

Kick It on DotNetKicks.com
blog comments powered by Disqus