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