1

rI know two tools : Value injector and Automapper. Each of them is currently supporting one of the feature, that i realy need. :) Background: i'm using stored procedure to load data from DB. relations 1 to many are handled as XML properties. Let's consider following example

    public class AutorDto
    {
        public string Name { get; set; }
        public List<Post> Posts { get; set; }
        public ICity City { get; set; }
    }
    public interface ICity
    {
        string Name { get; set; }
        string Code { get; set; }
    }
    public class CityDto
    {
        public string Name { get; set; }
        public string Code { get; set; }
    }

    public class PostDto
    {
        public DateTime Date { get; set; }
        public string Content { get; set; }
    }

I have stored procedure, that will return me this structure in following schema :

    public class Autor_From_Stored_Procedure
    {
        public string Name { get; set; }
        public string Posts { get; set; }
        public string CityName { get; set; }
        public string CityCode { get; set; }
    }

In order to map Autor_From_Stored_Procedure to AutorDto we must handle two topics:

  1. Deserializing from XML (i've managed to handle this problem using automapper. I've used this topic : Automapper to create object from XML) and this article : http://www.codeproject.com/Articles/706992/Using-AutoMapper-with-Complex-XML-Data

  2. Unflattening. It seems, that AutoMapper is not supporting convention based unflattening. Is, that true ? I saw, that there is posibility to declare some string (e.g. "City") as Unflattened object and everything should work - but value injector offers this as a convetion based standard: objectToIll.InjectFrom < UnflatLoopInjection>(object) - without neccessity to declare which properties [names in specific] would be deflattened) Is this also possible with automapper ?

If not, then maybe i should focus on value injector. If so - the problem from the 1) point is still valid (hopefully it is solved as easly as in automapper)

Penny for your thoughts!

@@Update

I've changed Dto definitions adding interface to City (as this is my case)

Community
  • 1
  • 1
  • I think for the first step all you need is an xml deserializer, not a mapper – Omu Aug 07 '15 at 12:42
  • @Omu, i came to the same conclusion (right now i'm using ValueInjecter) Nevertheless i have problem with basic object mapping(unflattening). Using example above: Mapper.AddMap(src => { var res = new AutorDto(); res.InjectFrom(src); //XML custom binding return res; }); property City on Autor is interface (ICity) I receive error : "Cannot create an instance of an interface" How to handle that ? – Piotr Filipowicz Aug 07 '15 at 14:26
  • UnflatLoopInjection assumes that all your unflattening properties are instantiable and without constructor parameters atm, so what you can do is not use Unflat, change property types, or grab the source code – Omu Aug 07 '15 at 15:15
  • @Omu, once again i agree (i've chosen third option). I modified Tunnelier to search for properties, which are interfaces and then to use (convention for naming dto classes (cut "I" at the begining, add "Dto" at the end) - e.g. ICity and class name would CityDto) class taken from Assembly.GetAssembly() It definietely works but it is somehow cheaty. Nevertheless thanks for advice. – Piotr Filipowicz Aug 07 '15 at 15:30

2 Answers2

1

I will post anwser to my question - maybe it will help someone. I've moved from Automapper to Valueinjecter. Xml binding was done using custom AddMap() and using XmlSerializer (no magic here)

But it turns out, that there will be problem with Interface on "Autor" class (and deflattening) You cannot simply create interface instance and this was the problem. I've modified slightly valueinjecter Tunnelier class to handle such case.

Firstly i've tried to cover this using somehow convention (if you find property ISomething, cut "I" add "Dto" But it is not elegant solution. So i ended up with custom : Inteface+Class mapping (so i'm pointing out : if you see ISomething interface, create instance of SomethingDto) Here is the modified code (maybe this could be added to valueinjecter implementation ?) The main "Mapper" class is not partial so i've defined new class for those mappings:

namespace Omu.ValueInjecter
{
    public partial class MapperActivations
    {
        public static ConcurrentDictionary<Type, Type> InterfaceActivations = new ConcurrentDictionary<Type, Type>();
        public static void AddInterfaceActivation<Interface, Class>()
        {
            InterfaceActivations.AddOrUpdate(typeof(Interface), typeof(Class), (key, oldValue) => typeof(Class));
        }
    }
}

Here are modified valueInjected classes:

    public static class TunnelierCustom
    {
        public static PropertyWithComponent Digg(IList<string> trail, object o)
        {
            var type = o.GetType();
            if (trail.Count == 1)
            {
                return new PropertyWithComponent { Component = o, Property = type.GetProperty(trail[0]) };
            }

            var prop = type.GetProperty(trail[0]);

            var val = prop.GetValue(o);

            if (val == null)
            {
                if (prop.PropertyType.IsInterface)
                {
                    if (MapperActivations.InterfaceActivations.ContainsKey(prop.PropertyType))
                    {
                        val = Activator.CreateInstance(MapperActivations.InterfaceActivations[prop.PropertyType]);
                    }
                    else
                    {
                        throw new Exception("Unable to create instance of: " + prop.PropertyType.Name + ". Are you missing InterfaceActivations bidning? Please add it using MapperActivations.AddInterfaceActivation<Interface, Class>() statement");
                    }
                }
                else
                {
                    val = Activator.CreateInstance(prop.PropertyType);
                }
                prop.SetValue(o, val);
            }

            trail.RemoveAt(0);
            return Digg(trail, val);
        }

        public static PropertyWithComponent GetValue(IList<string> trail, object o, int level = 0)
        {
            var type = o.GetType();

            if (trail.Count == 1)
            {
                return new PropertyWithComponent { Component = o, Property = type.GetProperty(trail[0]), Level = level };
            }

            var prop = type.GetProperty(trail[0]);
            var val = prop.GetValue(o);
            if (val == null) return null;
            trail.RemoveAt(0);
            return GetValue(trail, val, level + 1);
        }
    }

    /// <summary>
    /// performs flattening and unflattening
    /// first version of this class was made by Vadim Plamadeala ☺
    /// </summary>
    public static class UberFlatterCustom
    {
        public static IEnumerable<PropertyWithComponent> Unflat(string flatPropertyName, object target, Func<string, PropertyInfo, bool> match, StringComparison comparison)
        {
            var trails = TrailFinder.GetTrails(flatPropertyName, target.GetType().GetProps(), match, comparison, false).Where(o => o != null);

            return trails.Select(trail => TunnelierCustom.Digg(trail, target));
        }

        public static IEnumerable<PropertyWithComponent> Unflat(string flatPropertyName, object target, Func<string, PropertyInfo, bool> match)
        {
            return Unflat(flatPropertyName, target, match, StringComparison.Ordinal);
        }

        public static IEnumerable<PropertyWithComponent> Unflat(string flatPropertyName, object target)
        {
            return Unflat(flatPropertyName, target, (upn, pi) => upn == pi.Name);
        }

        public static IEnumerable<PropertyWithComponent> Flat(string flatPropertyName, object source, Func<string, PropertyInfo, bool> match)
        {
            return Flat(flatPropertyName, source, match, StringComparison.Ordinal);
        }

        public static IEnumerable<PropertyWithComponent> Flat(string flatPropertyName, object source, Func<string, PropertyInfo, bool> match, StringComparison comparison)
        {
            var trails = TrailFinder.GetTrails(flatPropertyName, source.GetType().GetProps(), match, comparison).Where(o => o != null);

            return trails.Select(trail => TunnelierCustom.GetValue(trail, source));
        }

        public static IEnumerable<PropertyWithComponent> Flat(string flatPropertyName, object source)
        {
            return Flat(flatPropertyName, source, (up, pi) => up == pi.Name);
        }
    }

    public class UnflatLoopCustomInjection : ValueInjection
    {
        protected override void Inject(object source, object target)
        {
            var sourceProps = source.GetType().GetProps();
            foreach (var sp in sourceProps)
            {
                Execute(sp, source, target);
            }
        }

        protected virtual bool Match(string upn, PropertyInfo prop, PropertyInfo sourceProp)
        {
            return prop.PropertyType == sourceProp.PropertyType && upn == prop.Name;
        }

        protected virtual void SetValue(object source, object target, PropertyInfo sp, PropertyInfo tp)
        {
            tp.SetValue(target, sp.GetValue(source));
        }

        protected virtual void Execute(PropertyInfo sp, object source, object target)
        {
            if (sp.CanRead)
            {
                var endpoints = UberFlatterCustom.Unflat(sp.Name, target, (upn, prop) => Match(upn, prop, sp)).ToArray();

                foreach (var endpoint in endpoints)
                {
                    SetValue(source, endpoint.Component, sp, endpoint.Property);
                }
            }
        }
    }

And this is example use :

       MapperActivations.AddInterfaceActivation<ICity, City>();
       Mapper.AddMap<Auto_From_Stored_Procedure, AutorDto>(src =>
            {
                var res = new User();
                res.InjectFrom<UnflatLoopCustomInjection>(src);
                res.Posts = Mapper.Map<XElement, List<Posts>>(src.PostsXml , "PostDto"); //this is mapping using XmlSerializer, PostsXml is XElement.Parse(Posts) in Autor_From_Stored_Procedure.
                return res;
            });
0

new version has been released 3.1

you can now specify an activator parameter for the UnflatLoopInjection

have a look at this unit test

Omu
  • 69,856
  • 92
  • 277
  • 407