12
\$\begingroup\$

I needed a mapper for a project I have using Dapper. I tried Automapper and other tools that comes with Dapper (Contrib, Rainbow, etc.), but I couldn't make Automapper work properly and the rest doesn't have what I need, so I did one.
Take in mind that I'm an amateur and not native english speaker ;)

The whole thing is in GitHub, I'll write down here what I can, but it's large, it won't fit the code limit so there's the link to the whole thing.
It works and in my tests it maps each object in about 2 milliseconds... it worry me, it's too much good.
To be honest, given that I'm an amateur and after reading lots about reflection performance overhead, I was "mentally prepared" to lose performance for the sake of having it working the way I wanted and what not, so now I'm worried if I'm overlooking something and will appreciate any advice.

I'm sorry, I tried to summarize the post but I can't summarize it any more, remember english is not my native language, and again the whole thing doesn't fit here. If you want to help (thanks you) or it interests you, you can just take a peek at the Github link I provided earlier and come back to read what you need from the rest, if you need it.

Just to be sure, both in code and here I'll refer as "Dapper's dynamic result" to the result of the query Dapper returns. I don't know if it's really suitable, but it's easier to me to name it that way.
Also, there's a general diagram I did to have things clearer myself: in google drive.

The mapper works given some static dictionaries that stores information about the types that will be mapped, using a configuration the user have to make before map any object (at the app beginning... similar to Automapper I think).
I divided the different dictionaries in different usual public classes, so each class of the mapper can inherit only what's needed, f.i.:

public class DMStatic_Dictionary : MQBStatic_IEnumerable
{
    public DMStatic_Dictionary(string[] customNamespaces = null) : base(customNamespaces)
    {
        InitStaticDDictionary();
    }

    #region fields
    protected static Dictionary<Type, Dictionary<string, string[]>> _Dictionaries;
    #endregion

    #region helpers
    private static void InitStaticDDictionary()
    {
        if (_Dictionaries == null)
        {
            lock (_LockObject)
            {
                if (_Dictionaries == null)
                    _Dictionaries = new Dictionary<Type, Dictionary<string, string[]>>();
            }
        }
    }
    #endregion
}

They store all the information, all the members, if each member is built-in, nested or a dictionary, prefixes and suffixes for searching later through the Dapper's dynamic result, members to ignore, etc. Lets say they are just static dictionaries with the "reflection information" of the types (nested types, enumerables, dictionaries, etc.), so I don't have to spend resources to retrieve that information at run time.


MAPPERCONFIG

As I said, the dictionaries should be filled at the very start of the app. To do that I made a fluent type class called MapperConfig.
This class do several things.

  • First it populates some static dictionaries through the public methods, f.i.:

    public MapperConfig AddNestedProperty<T>(bool isAnInterface, string memberName)
    {
        Type destination = typeof(T);
    
        if (_OnlyConstructor.Contains(destination))
            throw new CustomException_MapperConfig(
                @"MapperConfig.AddNestedProperty: You can't any configuration if MapOnlyConstructor have been setted.");
        if (_MembersCreators.ContainsKey(destination) && _MembersCreators[destination].ContainsKey(memberName))
            throw new CustomException_MapperConfig(
                $@"MapperConfig.AddNestedProperty: One member({memberName}) can not have a creator expression AND be setted as a nested type at same type");
        if (_Ignores.ContainsKey(destination) && _Ignores[destination].Contains(memberName))
            throw new CustomException_MapperConfig(
                $@"MapperConfig.AddNestedProperty: One member({memberName}) can not be setted as nested property if it was setted for ignore.");
    
        if (isAnInterface)
        {
            lock (_LockObject)
            {
                if (!_Interfaces.ContainsKey(destination))
                    _Interfaces.Add(destination, new List<string>() { memberName });
                else if (!_Interfaces[destination].Contains(memberName))
                    _Interfaces[destination].Add(memberName);
            }
        }
    
        lock (_LockObject)
        {
            if (!_NestedProperties.ContainsKey(destination))
            {
                _NestedProperties.Add(destination, new List<string>() { memberName });
                return this;
            }
            else
            {
                if (!_NestedProperties[destination].Contains(memberName))
                    _NestedProperties[destination].Add(memberName);
                return this;
            }
        }
    }
    

    This public methods are how one tell the mapper how to map each type member, if it's a nested type (AddNestedProperty), if it's a dictionary (AddDictionary), if it has to run some Func with the Dapper's dynamic result to create the member (AddMemberCreator), if it has to use a constructor (AddConstructor, works via a Func again), etc.
    I wrote the ones I think I'll need and others more general. Some notes about them:

    IEnumerable members are automated, the mapper will identify and map them automatically therefore there's no need to configure them and no method in this class for them, but I didn't find a way to automate dictionaries so I did a specific method to declare an object member as a dictionary to map it later: one need to pass the names that keys and values will have at the Dapper's dynamic result, so the mapper can find them at runtime. If keys or values (or both) are custom, nested types, one have to configure the member as a nested property too:

    .AddNestedProperty<Finca>(false, "_Cuotas") 
    .AddDictionary<Finca>("_Cuotas", new string[2] { "cuotaId", "Cuota" })
    

    The mapper will find what's nested (keys, values or both), map the built-in types using the name provided, ignore the name of the nested type and map the nested type recursively with a new mapper.

    Other note: as you probably have noted, a single member can be declared as various things, for instance (again) if a member is of type Dictionary<MyClass, int>, not only it can be declared as dictionary and nested, it MUST be declared as both of them or later the mapper will try to map keys as a built-in type and throw an exception.

  • Second it populates more static dictionaries with reflection. I didn't want to search and retrieve all member of all objects-to-map at run time, so I store every member of the objects configured in the dictionaries:

    protected static Dictionary<Type, PropertyInfo[]> _QBPropertyInfos;
    protected static Dictionary<Type, FieldInfo[]> _QBFieldInfos;
    

    This is done in the method private void SetMembersInformation(Type t) of the MapperConfig class:

    private void SetMembersInformation(Type t)
    {
        if (_OnlyConstructor.Contains(t))
            RemoveAllConfigExceptConstructor(t);
    
        var pInfos = t.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
            .Where(pInfo => pInfo.GetSetMethod() != null)
            .ToList();
        var fInfos = t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
            //http://stackoverflow.com/questions/40820102/reflection-returns-backing-fields-of-read-only-properties
            .Where(fInfo => fInfo.GetCustomAttribute<CompilerGeneratedAttribute>() == null)
            .ToList();
    
        //Get all inherited fields up the hierarchy => BindingFlags.FlattenHierarchy only works with public members
        bool inheritance = t.BaseType != null;
        Type inheritedT = t;
        Type baseT;
        while (inheritance)
        {
            //inherited fields
            baseT = inheritedT.BaseType;
            var baseFInfos = baseT.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
                .Where(fInfo => fInfo.GetCustomAttribute<CompilerGeneratedAttribute>() == null)
                .ToList();
            fInfos = fInfos.Union(baseFInfos).ToList();
    
            //inherit mapper configurations
            MapperStore store = new MapperStore();
            if (store.GetMapper(baseT) != null)
                CopyConfigurations(baseT, t);
    
            inheritance = baseT.BaseType != null;
            inheritedT = baseT;
        }
    
        IEnumerable<MemberInfo> mInfos = pInfos.Union((IEnumerable<MemberInfo>)fInfos);
        Dictionary<MemberInfo, MemberTypeInfo> preMTInfos = (Dictionary<MemberInfo, MemberTypeInfo>)mInfos
            .Select(x =>
            {
                if (_Ignores.ContainsKey(t) && _Ignores[t].Contains(x.Name))
                    return new KeyValuePair<MemberInfo, MemberTypeInfo>(x, MemberTypeInfo.Ignore);
                else return new KeyValuePair<MemberInfo, MemberTypeInfo>(x, MemberTypeInfo.BuiltIn);
            })
            .ToDictionary(x => x.Key, x => x.Value);
    
        //prevent collection was modified exception
        Dictionary<MemberInfo, MemberTypeInfo> changes = new Dictionary<MemberInfo, MemberTypeInfo>(preMTInfos);
    
        IEnumerable<string> preNamesList = pInfos
            .Where(pInfo => preMTInfos[pInfo] != MemberTypeInfo.IEnumerable)
            .Select(pInfo => pInfo.Name)
            .Union(fInfos
                .Where(pInfo => preMTInfos[pInfo] != MemberTypeInfo.IEnumerable)
                .Select(fInfo => fInfo.Name));
    
        //Store all MemberTypeInfo
        //Trying to save iterations doing first if dictionary.contains(type)
        if (_MembersCreators.ContainsKey(t) && _NestedProperties.ContainsKey(t))
        {
            //Set members type dictionary
            foreach (KeyValuePair<MemberInfo, MemberTypeInfo> kvp in preMTInfos.Where(kvp => kvp.Value != MemberTypeInfo.Ignore))
            {
                if (_MembersCreators[t].ContainsKey(kvp.Key.Name))
                {
                    if (!_OnlyConstructor.Contains(t)) changes[kvp.Key] = MemberTypeInfo.Creator;//_preMTInfos[kvp.Key] = MemberTypeInfo.Creator;
                    RemoveMember(kvp.Key, pInfos, fInfos);
                }
                else
                {
                    if (_NestedProperties[t].Contains(kvp.Key.Name))
                    {
                        if (!_OnlyConstructor.Contains(t)) changes[kvp.Key] = MemberTypeInfo.Nested;//_preMTInfos[kvp.Key] = MemberTypeInfo.Nested;
                        RemoveMember(kvp.Key, pInfos, fInfos);
                    }
    
                    Type mType = GetMemberType(kvp.Key);
                    if (typeof(IEnumerable).IsAssignableFrom(mType) && !typeof(string).IsAssignableFrom(mType))
                    {
                        if (!_OnlyConstructor.Contains(t)) changes[kvp.Key] = changes[kvp.Key] | MemberTypeInfo.IEnumerable; //_preMTInfos[kvp.Key] = _preMTInfos[kvp.Key] | MemberTypeInfo.IEnumerable;
                        RemoveMember(kvp.Key, pInfos, fInfos);
                    }
                }
            }
        }
        else if (_MembersCreators.ContainsKey(t))
        {
            //Set members type dictionary
            foreach (KeyValuePair<MemberInfo, MemberTypeInfo> kvp in preMTInfos.Where(kvp => kvp.Value != MemberTypeInfo.Ignore))
            {
                if (_MembersCreators[t].ContainsKey(kvp.Key.Name))
                {
                    if (!_OnlyConstructor.Contains(t)) changes[kvp.Key] = MemberTypeInfo.Creator; //_preMTInfos[kvp.Key] = MemberTypeInfo.Creator;
                    RemoveMember(kvp.Key, pInfos, fInfos);
                }
                else
                {
                    Type mType = GetMemberType(kvp.Key);
                    if (typeof(IEnumerable).IsAssignableFrom(mType) && !typeof(string).IsAssignableFrom(mType))
                    {
                        if (!_OnlyConstructor.Contains(t)) changes[kvp.Key] = changes[kvp.Key] | MemberTypeInfo.IEnumerable; //_preMTInfos[kvp.Key] = _preMTInfos[kvp.Key] | MemberTypeInfo.IEnumerable;
                        RemoveMember(kvp.Key, pInfos, fInfos);
                    }
                }
            }
        }
        else if (_NestedProperties.ContainsKey(t))
        {
            //Add to members names list
            preNamesList = preNamesList.Union(_NestedProperties[t]);
    
            //Set members type dictionary
            foreach (KeyValuePair<MemberInfo, MemberTypeInfo> kvp in preMTInfos.Where(kvp => kvp.Value != MemberTypeInfo.Ignore))
            {
                if (_NestedProperties[t].Contains(kvp.Key.Name))
                {
                    if (!_OnlyConstructor.Contains(t)) changes[kvp.Key] = MemberTypeInfo.Nested; //_preMTInfos[kvp.Key] = MemberTypeInfo.Nested;
                    RemoveMember(kvp.Key, pInfos, fInfos);
                }
    
                Type mType = GetMemberType(kvp.Key);
                if (typeof(IEnumerable).IsAssignableFrom(mType) && !typeof(string).IsAssignableFrom(mType))
                {
                    if (!_OnlyConstructor.Contains(t)) changes[kvp.Key] = changes[kvp.Key] | MemberTypeInfo.IEnumerable; //_preMTInfos[kvp.Key] = _preMTInfos[kvp.Key] | MemberTypeInfo.IEnumerable;
                    RemoveMember(kvp.Key, pInfos, fInfos);
                }
            }
        }
        //built-in
        else if (!_OnlyConstructor.Contains(t))
        {
            //Set members type dictionary
            foreach (KeyValuePair<MemberInfo, MemberTypeInfo> kvp in preMTInfos.Where(kvp => kvp.Value != MemberTypeInfo.Ignore))
            {
                Type mType = GetMemberType(kvp.Key);
                if (typeof(IEnumerable).IsAssignableFrom(mType) && !typeof(string).IsAssignableFrom(mType))
                    changes[kvp.Key] = MemberTypeInfo.IEnumerable; //_preMTInfos[kvp.Key] = MemberTypeInfo.IEnumerable;
            }
        }
    
        if (_Interfaces.ContainsKey(t))
        {
            //Set members type dictionary
            foreach (KeyValuePair<MemberInfo, MemberTypeInfo> kvp in preMTInfos.Where(kvp => kvp.Value != MemberTypeInfo.Ignore))
            {
                if (_Interfaces[t].Contains(kvp.Key.Name))
                {
                    if (!_OnlyConstructor.Contains(t)) changes[kvp.Key] = changes[kvp.Key] | MemberTypeInfo.Interface;
                    RemoveMember(kvp.Key, pInfos, fInfos);
                }
    
                Type mType = GetMemberType(kvp.Key);
                if (typeof(IEnumerable).IsAssignableFrom(mType) && !typeof(string).IsAssignableFrom(mType))
                {
                    if (!_OnlyConstructor.Contains(t)) changes[kvp.Key] = changes[kvp.Key] | MemberTypeInfo.IEnumerable; //_preMTInfos[kvp.Key] = _preMTInfos[kvp.Key] | MemberTypeInfo.IEnumerable;
                    RemoveMember(kvp.Key, pInfos, fInfos);
                }
            }
        }
    
        //Lock-static dictionaries
        lock (_LockObject)
        {
            if (!_mtInfos.ContainsKey(t)) _mtInfos.Add(t, changes);
            else _mtInfos[t] = changes;
    
            if (!_NamesList.ContainsKey(t)) _NamesList.Add(t, preNamesList);
            else _NamesList[t] = preNamesList;
    
            if (!_QBPropertyInfos.ContainsKey(t)) _QBPropertyInfos.Add(t, pInfos.ToArray());
            else _QBPropertyInfos[t] = pInfos.ToArray();
    
            if (!_QBFieldInfos.ContainsKey(t)) _QBFieldInfos.Add(t, fInfos.ToArray());
            else _QBFieldInfos[t] = fInfos.ToArray();
    
            string columns = "";
            var orderedMembersNames = _QBPropertyInfos[t].Select(x => x.Name)
                .Union(_QBFieldInfos[t].Select(x => QBuilder.StringSQLBuilder.RemoveFieldsUnderscore(x.Name)))
                .OrderBy(x => x);
            columns = string.Join(",", orderedMembersNames);
    
            if (!_Columns.ContainsKey(t)) _Columns.Add(t, columns);
            else _Columns[t] = columns;
        }
    }
    

    It iterates through all members of the given type (and parent classes), storing them, determining who are an IEnumerable and filling another important dictionary:

    protected static Dictionary<Type, Dictionary<MemberInfo, MemberTypeInfo>> _mtInfos;
    

    Which is just a fast way to determine at runtime what have to do the mapper.
    MemberTypeInfo is an Enum:

    [Flags]
    public enum MemberTypeInfo { BuiltIn = 1, Nested = 2, Creator = 4, IEnumerable = 8, Dictionary = 16, Interface = 32, Ignore = 64 }
    

    So, at runtime the mapper looks at this dictionary for every member, and does whatever it has to do, e.g. if the member is a nested type it calls another mapper recursively to map the member, and if it's built-in type it just search the value in the given Dapper dynamic result by the member's name and sets it.
    The configurations of the parent classes are inherited too through the private void CopyConfigurations(Type baseT, Type nestedT) method, so one can configure a parent class completely (even an abstract class), and then configure all the child class without having to repeat the parent's things, since all child will automatically inherit parent configurations.

There're some obvious configurations like AddNestedProperty or IgnoreProperty, and others not so obvious like AddInterfaceToObjectCondition but I think that the methods comments are enough to understand what they all do.
If that's not the case I'll explain anything, just ask it.

Just to note a pair of rules here (or flaws...):

  1. The method EndConfig() must be called for every type that will have to be mapped, even nested types, interfaces, structs, everything. Even if the type have only built in types and needs no other configuration. The mapper only "see" what's configured, if EndConfig() doesn't get called for a type, that type doesn't exist for the mapper.
  2. The same method must be called after all configurations for that type have been called. Given that it's fluent type, the last method for a type configuration should be always EndConfig(). Take in mind that the method SetMembersInformation compares members with what the user stored in dictionaries, so if that method is called various times, I have no idea what could happen, it's not prepared for that.
  3. I wouldn't make configurations at run time. The system haven't been made for that, I haven't tested it and I have no ides what would happen. Maybe it'd work flawlessly, or maybe not.

A pair of example of some configurations of my own project:

//A pair of easy ones
            mConfig
                .AddNestedProperty<Apunte>(false, "_Asiento")
                .AddMemberCreator<Apunte>("_DebeHaber", x => (DebitCredit)x.DebeHaber)
                .AddPrefixes<Apunte>(new string[] { "apu" })
                .EndConfig<Apunte>();
            mConfig
                .AddConstructor<ApunteDLO>(x => new ApunteDLO(x.Id, x.IdOwnerComunidad, x.OrdenEnAsiento, x.Asiento, x.Concepto, x.DebeHaber,
                    x.Importe, x.IdCuenta, x.Punteo, x.Factura))
                .MapOnlyConstructor<ApunteDLO>()
                .EndConfig<ApunteDLO>();

...

//More complicated
            mConfig
                .AddConstructor<Finca>(x => new Finca(x.Id, x.IdOwnerComunidad, x.Baja, x.Nombre, x.Coeficiente, x.Codigo, this.ACData))
                .AddNestedProperty<Finca>(false, "_PropietarioActual")
                .AddDictionary<Finca>("_HistoricoPropietarios", new string[2] { "FechaFinal", "IdPropietario" })
                .AddNestedProperty<Finca>(false, "_Cuotas")
                .AddDictionary<Finca>("_Cuotas", new string[2] { "cuotaId", "Cuota" })
                .AddNestedProperty<Finca>(false, "_EntregasACuenta")
                .AddNestedProperty<Finca>(false, "_Devoluciones")
                .AddNestedProperty<Finca, DireccionPostal>(false, x => x.Direccion)
                .AddMemberCreator<Finca, DireccionPostal>(x => x.Direccion2,
                    x => new DireccionPostal(x.TipoVia2, x.Direccion2, x.CP2, x.Localidad2, x.Provincia2))
                .AddMemberCreator<Finca, TipoPagoCuotas>(x => x.DefaultTipoPagoCuotas, x => (TipoPagoCuotas)x.DefaultTipoPagoCuotas)
                .AddMemberCreator<Finca, sTelefono>(x => x.Telefono1, x => new sTelefono(x.Telefono, x.TipoTelefono))
                .AddMemberCreator<Finca, sTelefono>(x => x.Telefono2, x => new sTelefono(x.Telefono2, x.TipoTelefono2))
                .AddMemberCreator<Finca, sTelefono>(x => x.Telefono3, x => new sTelefono(x.Telefono3, x.TipoTelefono3))
                .AddMemberCreator<Finca, sTelefono>(x => x.Fax, x => new sTelefono(x.Fax, TipoTelefono.Fax))
                .AddMemberCreator<Finca, int[]>(x=>x.IdAsociadas, x=>
                {
                    IEnumerable<dynamic> ex = (IEnumerable<dynamic>)x;
                    return ex
                        .Select(dyn => dyn.IdFincaAsociada)
                        .Distinct()
                        .ToArray();
                })
                .AddMemberCreator<Finca>("_IdCopropietarios", x =>
                {
                    IEnumerable<dynamic> ex = (IEnumerable<dynamic>)x;
                    return ex
                        .Select(dyn => dyn.IdPersonaCoProp)
                        .Distinct()
                        .ToArray();
                })
                .AddMemberCreator<Finca>("_IdPagadores", x =>
                {
                    IEnumerable<dynamic> ex = (IEnumerable<dynamic>)x;
                    return ex
                        .Select(dyn => new Tuple<int, TipoPagoCuotas>(dyn.IdPersonaCoPag, (TipoPagoCuotas)dyn.TipoPagoCuotas))
                        .Distinct()
                        .ToArray();
                })
                .EndConfig<Finca>();

MAPPERSTORE

I thought it would be a waste to create mappers all around the app, so I just created a little class called MapperStore to store and retrieve mappers already created, using a static dictionary to store mappers as interfaces.

/// <summary>
/// Use this to get DapperMapper objects.
/// </summary>
public class MapperStore : DMStatic_Store
{
    /// <summary>
    /// Store t as a type that can be, and has been configured for mapping by DapperMapper. If you are configuring a type, use 
    /// MapperConfig.EndCongfig<type>() instead.
    /// Don't use it before configuring the mapper first.
    /// </summary>
    /// <param name="t"></param>
    public void StoreType(Type t)
    {
        if (!_TypesToMap.Contains(t))
            _TypesToMap.Add(t);
    }
    /// <summary>
    /// Store a mapper object in static dictionary.
    /// </summary>
    /// <param name="t"></param>
    /// <param name="mapper"></param>
    public void StoreMapper(Type t, iDapperMapper mapper)
    {
        if (!_Mappers.ContainsKey(t)) _Mappers.Add(t, mapper);
    }
    /// <summary>
    /// Get mapper of type t. If type t hasn't been stored by StoreType, returns null. If type t has been stored and there are no mappers
    /// created yet, it creates a new one, stores it, and return it.
    /// </summary>
    /// <param name="t"></param>
    /// <returns></returns>
    public iDapperMapper GetMapper(Type t)
    {
        if (_Mappers.ContainsKey(t))
            return _Mappers[t];

        if (!_TypesToMap.Contains(t))
            return null;

        iDapperMapper mapper = (iDapperMapper)Activator.CreateInstance(typeof(DapperMapper<>).MakeGenericType(t), this);
        StoreMapper(t, mapper);
        return mapper;
    }
    /// <summary>
    /// Returns true if a mapper exists or can be created, and set it as iDapperMapper.
    /// If type t hasn't been stored by StoreType, returns false. If type t has been stored and there are no mappers
    /// created yet, it creates a new one, stores it, set it as iDapperMapper and returns true.
    /// </summary>
    /// <param name="t"></param>
    /// <param name="mapper"></param>
    /// <returns></returns>
    public bool GetMapper(Type t, out iDapperMapper mapper)
    {
        if (_Mappers.ContainsKey(t))
        {
            mapper = _Mappers[t];
            return true;
        }

        if (!_TypesToMap.Contains(t))
        {
            mapper = null;
            return false;
        }

        mapper = (iDapperMapper)Activator.CreateInstance(typeof(DapperMapper<>).MakeGenericType(t), this);
        StoreMapper(t, mapper);
        return false;
    }
    /// <summary>
    /// Remove mapper previously stored.
    /// </summary>
    /// <param name="t"></param>
    public void RemoveMapper(Type t)
    {
        if (_Mappers.ContainsKey(t))
            _Mappers.Remove(t);
    }
}

And the expected way to get a mapper:

MapperStore store = new MapperStore();
DapperMapper<Foo> mapper = (DapperMapper<Foo>)store.GetMapper(typeof(Foo));

DAPPERMAPPER

And here's the thing finally, it's the class that actually map the objects, usually through this method:

        public T Map(IEnumerable<dynamic> dapperResult, bool cleanResult = false)
        {
            var parser = new PrePostFixesParser(this);
            T mapped = this.NewObject(dapperResult.First());
            if (_OnlyConstructor.Contains(this.TType)) return mapped;

            //TODO: divide el siguiente foreach en dos con dos nuevos diccionarios estáticos, uno para pInfos y otro para fInfos, 
            //aunque se repita código: hacer métodos para cada parte del código del tipo:
            //private T PreMapCreator(KeyValuePair<PropertyInfo, MemberTypeInfo> kvp, IEnumerable<dynamic> dapperResult, bool cleanResult = false)
            //private T PreMapIEnumerable(KeyValuePair<PropertyInfo, MemberTypeInfo> kvp, IEnumerable<dynamic> dapperResult, bool cleanResult = false)
            //...

            //Loop through all members
            foreach (KeyValuePair<MemberInfo, MemberTypeInfo> kvp in mtInfos)
            {
                if (kvp.Value == MemberTypeInfo.Ignore)
                    continue;
                //Member have a creator
                else if ((kvp.Value & MemberTypeInfo.Creator) == MemberTypeInfo.Creator)
                {
                    //MemberDelegate mDel = (MemberDelegate)_MembersCreators[this.TType][kvp.Key.Name];
                    Func<dynamic, object> mDel = (Func<dynamic, object>)_MembersCreators[this.TType][kvp.Key.Name];

                    if (kvp.Key.MemberType == MemberTypes.Property) ((PropertyInfo)kvp.Key).SetValue(mapped, mDel(dapperResult));
                    else ((FieldInfo)kvp.Key).SetValue(mapped, mDel(dapperResult));
                }
                //Member is IDictionary or IEnumerable
                else if ((kvp.Value & MemberTypeInfo.IEnumerable) == MemberTypeInfo.IEnumerable)
                {
                    Type t = GetMemberType(kvp.Key);
                    //if ((kvp.Value & MemberTypeInfo.Interface) == MemberTypeInfo.Interface) t = ResolveInterface(kvp.Key, dapperResult);
                    //else t = GetMemberType(kvp.Key);
                    /*
                    {
                        //Type of property or field
                        if (kvp.Key.MemberType == MemberTypes.Property) t = ((PropertyInfo)kvp.Key).PropertyType;
                        else t = ((FieldInfo)kvp.Key).FieldType;
                    }*/
                    bool isAnInterface = (kvp.Value & MemberTypeInfo.Interface) == MemberTypeInfo.Interface;
                    bool isNested = (kvp.Value & MemberTypeInfo.Nested) == MemberTypeInfo.Nested;

                    //If member is a dictionary
                    if (typeof(IDictionary).IsAssignableFrom(t))
                    {
                        //Create a dummy dictionary with the dapper's dynamic result which should be equal to the final one
                        DictionaryMapper dictMapper = new DictionaryMapper(dapperResult, kvp.Key.Name, isNested, isAnInterface, cleanResult, t, this);

                        try
                        {
                            if (kvp.Key.MemberType == MemberTypes.Property) ((PropertyInfo)kvp.Key).SetValue(mapped, dictMapper.DummyDictionary);
                            else ((FieldInfo)kvp.Key).SetValue(mapped, dictMapper.DummyDictionary);
                        }
                        catch (Exception err)
                        {
                            throw new CustomException_DapperMapper(
                                $@"DapperMapper.Map: Couldn't map IDictionary member {kvp.Key.Name} with value contained by dynamic object.
Incorrect type of value?: {kvp.Value.ToString()}",
                                err);
                        }
                    }
                    //Rest of enumerables
                    else
                    {
                        IEnumerable<dynamic> iEnumDapperResult;
                        //Select current member's values from dynamic
                        if (isNested && !cleanResult)
                        {
                            //Type mType = t; // GetMemberType(kvp.Key);//IEnumerable<T>
                            Type genericType = t.GenericTypeArguments[0];//mType.GenericTypeArguments[0];//T
                            if ((kvp.Value & MemberTypeInfo.Interface) == MemberTypeInfo.Interface)
                            {
                                bool genericIsInterfaceNotIEnumerable =
                                    genericType.IsInterface &&
                                    !typeof(IDictionary).IsAssignableFrom(genericType) &&
                                    !(typeof(IEnumerable).IsAssignableFrom(genericType) && !typeof(string).IsAssignableFrom(genericType));

                                if (genericIsInterfaceNotIEnumerable) genericType = ResolveInterface(genericType, dapperResult);
                            }

                            iDapperMapper nestedMapper = MappersStore.GetMapper(genericType);
                            var nestedParser = new PrePostFixesParser(nestedMapper);

                            iEnumDapperResult = dapperResult
                                .Select(dyn => nestedParser.GetTypeMembersWithoutPrePostFixes(dyn, nestedMapper.NamesList));
                        }
                        else if (!cleanResult) iEnumDapperResult = dapperResult.Select(dyn => parser.RemovePrePostFixesFromDictionary(dyn));
                        else iEnumDapperResult = dapperResult;

                        //Create dummy IEnumerable
                        EnumerableMapper enumMapper = new EnumerableMapper(iEnumDapperResult, kvp.Key.Name, isNested, t, this.TType); ;
                        var dummy = Activator.CreateInstance(t, enumMapper.DummyEnumerable);

                        try
                        {
                            if (kvp.Key.MemberType == MemberTypes.Property) ((PropertyInfo)kvp.Key).SetValue(mapped, dummy);
                            else ((FieldInfo)kvp.Key).SetValue(mapped, dummy);
                        }
                        catch (Exception err)
                        {
                            throw new CustomException_DapperMapper(
                                $@"DapperMapper.Map: Couldn't map IEnumerable member {kvp.Key.Name} with value contained by dynamic object.
Incorrect type of value?: {kvp.Value.ToString()}",
                                err);
                        }
                    }
                }//End IDictionary/IEnumerable
                //If Built-in
                else if ((kvp.Value & MemberTypeInfo.BuiltIn) == MemberTypeInfo.BuiltIn)
                {
                    string name = parser.RemoveFieldsUnderscore(kvp.Key.Name);
                    IDictionary<string, object> dapperDict;
                    if (!cleanResult)
                        dapperDict = parser.GetTypeMembersWithoutPrePostFixes(dapperResult.First(), NamesList) as IDictionary<string, object>;
                    else
                        dapperDict = dapperResult.First() as IDictionary<string, object>;

                    if (!dapperDict.ContainsKey(name))
                        throw new CustomException_DapperMapper(
                            $@"DapperMapper.Map: There's no member in dynamic dapper result with name {kvp.Key.Name}. Cannot Map object.");

                    try
                    {
                        if (kvp.Key.MemberType == MemberTypes.Property) ((PropertyInfo)kvp.Key).SetValue(mapped, dapperDict[name]);
                        else ((FieldInfo)kvp.Key).SetValue(mapped, dapperDict[name]);
                    }
                    catch (Exception err)
                    {
                        throw new CustomException_DapperMapper(
                            $@"DapperMapper.Map: Couldn't map BuiltIn-type member {kvp.Key.Name} with value contained by dynamic object.
Incorrect type of value?: {kvp.Value.ToString()}",
                            err);
                    }
                }
                //if nested
                else if ((kvp.Value & MemberTypeInfo.Nested) == MemberTypeInfo.Nested)
                {
                    Type mType = GetMemberType(kvp.Key);

                    if ((kvp.Value & MemberTypeInfo.Interface) == MemberTypeInfo.Interface)
                        mType = ResolveInterface(mType, dapperResult);

                    //access generic Map method through nongeneric interface method
                    iDapperMapper nestedMapper = MappersStore.GetMapper(mType);

                    if (nestedMapper == null)
                        throw new CustomException_DapperMapper(
                            $@"DapperMapper.Map: No Mapper found at store for property {kvp.Key.Name} of type {mType.ToString()}.
If you want to map a nested property you have to create a mapper for that property type.");

                    if (kvp.Key.MemberType == MemberTypes.Property)
                        ((PropertyInfo)kvp.Key).SetValue(mapped, nestedMapper.NoGenericMap(dapperResult, cleanResult));
                    else ((FieldInfo)kvp.Key).SetValue(mapped, nestedMapper.NoGenericMap(dapperResult, cleanResult));
                }
            }

            return mapped;
        }

The idea is to use:

MapperStore store = new MapperStore();
DapperMapper<Persons> mapper = (DapperMapper<Persons>)store.GetMapper(typeof(Persons));
Persons p = mapper.Map(dynamicDapperResult);

Due to it being generic and that the method has to recursively call other DapperMapper.Map methods, I use a non-generic interface to call the mappers while mapping at run time, using the NoGenericMap methods.
The generic DapperMapper<T> class actually maps the objects and is called at runtime. It uses the dictionaries to know if a member is nested or built-in, if it's an interface, if it's an IEnumerable or IDictionary, if it have to use a Func to create a member, etc. It actually does very little with reflection, the minimum I could manage to achieve.
Basically it first create an instance of the object (via reflection or with the configured constructor), then loops through all class members, search in the dictionaries for adequate type and member using the MemberTypeInfo Enum I spoke about above and fill the member according to what was configured following the priority showed in the diagram. If a member is a nested type, just create a new mapper and again and again.
IEnumerableand IDictionary members have a their own classes to map them, EnumerableMapper and DictionaryMapper. They simply create an instance of the adequate object and search the data to map all the collection members through the dynamic Dapper's result, which will call again a normal DapperMapper if they are configured as nested types.

There's an overloaded method to map enumerables:

public R Map<R>(IEnumerable<dynamic> dapperResult, string splitOn = "Id", bool cleanResult = false)
        where R : IEnumerable<T>

One must call this method if want to map a whole IEnumerable of objects:

List<Persons> myList = mapper.Map<List<Persons>>(dapperResult, "Id");

I didn't find other way to do this except with an overloaded method.

In general the Dapper result dynamic members must have the same name as the object to be mapped members. I wrote the code to support prefixes and sufixes, that can be added via MapperConfig, and a parser to "decode" them at runtime.
The parser:

public class PrePostFixesParser
{
    public PrePostFixesParser(iDapperMapper mapper)
    {
        Tuple<string[], bool> prefixes = mapper != null ? mapper.Prefixes : null;
        Tuple<string[], bool> postfixes = mapper != null ? mapper.Postfixes : null;

        if (prefixes != null)
        {
            this.Prefixes = prefixes.Item1;
            this.PrefixesExclusive = prefixes.Item2;
            this.PrefixesCount = prefixes.Item1.Select(x => x.Count()).Distinct();
        }
        else
        {
            this.Prefixes = null;
            this.PrefixesExclusive = false;
            this.PrefixesCount = new int[] { 0 };
        }

        if (postfixes != null)
        {
            this.Postfixes = postfixes.Item1;
            this.PostfixesExclusive = postfixes.Item2;
            this.PostfixesCount = postfixes.Item1.Select(x => x.Count()).Distinct();
        }
    }

    #region properties
    public string[] Prefixes { get; private set; }
    public string[] Postfixes { get; private set; }
    public bool PrefixesExclusive { get; private set; }
    public bool PostfixesExclusive { get; private set; }
    public IEnumerable<int> PrefixesCount { get; private set; }
    public IEnumerable<int> PostfixesCount { get; private set; }
    #endregion

    #region helpers
    private bool RemovePrefixIfStringContains(ref string str)
    {
        if (Prefixes == null)
            return false;

        bool contain = false;
        foreach (int count in PrefixesCount)
        {
            //contains some of the configurated prefixes
            contain = Prefixes.Contains(str.Substring(0, count));
            if (contain)
            {
                str = str.Remove(0, count);
                return contain;
            }
        }

        return contain;
    }
    private bool RemovePostfixIfStringContains(ref string str)
    {
        if (Postfixes == null)//(PostfixesCount.Count() == 1 && PostfixesCount.First() == 0)
            return false;

        bool contain = false;
        foreach (int count in PostfixesCount)
        {
            contain = Postfixes.Contains(str.Substring(str.Length - count, count));
            if (contain)
            {
                str = str.Remove(str.Length - count, count);
                return contain;
            }
        }
        return contain;
    }
    private bool KeysContainsSomePrePostfix(IDictionary<string, object> dict)
    {
        if (Prefixes != null && Postfixes != null)
        {
            return dict.Any(kvp =>
            {
                foreach (int count in PrefixesCount)
                {
                    //contains some of the configurated prefixes or postfixes
                    if (Prefixes.Contains(kvp.Key.Substring(0, count))
                        || Postfixes.Contains(kvp.Key.Substring(kvp.Key.Length - count, count)))
                        return true;
                    else return false;
                }
                return false;
            });
        }
        else if (Prefixes != null)
        {
            return dict.Any(kvp =>
            {
                foreach (int count in PrefixesCount)
                {
                    //contains some of the configurated prefixes
                    if (Prefixes.Contains(kvp.Key.Substring(0, count)))
                        return true;
                    else return false;
                }
                return false;
            });
        }
        else if (Postfixes != null)
        {
            return dict.Any(kvp =>
            {
                foreach (int count in PrefixesCount)
                {
                    //contains some of the configurated postfixes
                    if (Postfixes.Contains(kvp.Key.Substring(kvp.Key.Length - count, count)))
                        return true;
                    else return false;
                }
                return false;
            });
        }
        return false;
        //return dict.Any(kvp => (Prefixes.Contains(kvp.Key.Substring(0,)
    }
    #endregion

    #region public methods
    public string RemoveFieldsUnderscore(string str)
    {
        if (str.StartsWith("_")) return str.Remove(0, 1);
        return str;
    }
    public IEnumerable<string> GetCleanNamesList(IEnumerable<string> namesList)
    {
        return namesList.Select(str => RemoveFieldsUnderscore(str));
    }
    public IEnumerable<IDictionary<string, object>> RemovePrePostFixesFromDictionary(IEnumerable<dynamic> dyn)
    {
        List<Dictionary<string, object>> ienum = new List<Dictionary<string, object>>();
        foreach (dynamic d in dyn)
        {
            Dictionary<string, object> dict = new Dictionary<string, object>();
            foreach (KeyValuePair<string, object> kvp in (d as IDictionary<string, object>))
            {
                string keyWithoutPrePostfixes = kvp.Key;
                if (!RemovePrefixIfStringContains(ref keyWithoutPrePostfixes) && !RemovePostfixIfStringContains(ref keyWithoutPrePostfixes))
                    dict.Add(kvp.Key, kvp.Value);
                else
                    dict.Add(keyWithoutPrePostfixes, kvp.Value);//return new KeyValuePair<string, object>(keyWithoutPrePostfixes, kvp.Value);
            }
            ienum.Add(dict);
        }

        return ienum;
    }
    public dynamic GetTypeMembersWithoutPrePostFixes(dynamic dyn, IEnumerable<string> namesList)
    {
        var typeMembers = new ExpandoObject() as IDictionary<string, object>;
        IDictionary<string, object> dict = dyn as IDictionary<string, object>;
        IEnumerable<string> names = namesList.Select(str => RemoveFieldsUnderscore(str));

        int i = 0;
        if (!PrefixesExclusive && !PostfixesExclusive)
        {
            foreach (KeyValuePair<string, object> kvp in dict)
            {
                string keyWithoutPrePostfixes = kvp.Key;
                bool namesContainsKey = names.Contains(kvp.Key);

                if (!RemovePrefixIfStringContains(ref keyWithoutPrePostfixes)
                    && !RemovePostfixIfStringContains(ref keyWithoutPrePostfixes)
                    && namesContainsKey)
                {
                    typeMembers.Add(kvp.Key, kvp.Value);
                    i++;
                }
                else if (names.Contains(keyWithoutPrePostfixes))
                {
                    typeMembers.Add(keyWithoutPrePostfixes, kvp.Value);
                    i++;
                }
            }
        }
        else if (PrefixesExclusive && PostfixesExclusive)
        {
            foreach (KeyValuePair<string, object> kvp in dict)
            {
                string keyWithoutPrePostfixes = kvp.Key;
                if (RemovePrefixIfStringContains(ref keyWithoutPrePostfixes)
                    && RemovePostfixIfStringContains(ref keyWithoutPrePostfixes)
                    && names.Contains(keyWithoutPrePostfixes))
                    typeMembers.Add(keyWithoutPrePostfixes, kvp.Value);
            }
        }
        else if (PrefixesExclusive)
        {
            foreach (KeyValuePair<string, object> kvp in dict)
            {
                string keyWithoutPrePostfixes = kvp.Key;
                if (RemovePrefixIfStringContains(ref keyWithoutPrePostfixes))
                {
                    RemovePostfixIfStringContains(ref keyWithoutPrePostfixes);

                    if (names.Contains(keyWithoutPrePostfixes))
                        typeMembers.Add(keyWithoutPrePostfixes, kvp.Value);
                }
            }
        }
        else
        {
            foreach (KeyValuePair<string, object> kvp in dict)
            {
                string keyWithoutPrePostfixes = kvp.Key;
                if (RemovePostfixIfStringContains(ref keyWithoutPrePostfixes))
                {
                    RemovePrefixIfStringContains(ref keyWithoutPrePostfixes);

                    if (names.Contains(keyWithoutPrePostfixes))
                        typeMembers.Add(keyWithoutPrePostfixes, kvp.Value);
                }
            }
        }

        return typeMembers;
    }
    #endregion
}

And that's all, I think. If you are still reading this I believe I have to thank you and congratulate you :P
Besides that, I'll just post the test I did that I mentioned at the beginning (and the results with a simple object and with an object with dictionary members), and that will be all:

        public static void MapperTest<T>(int testsTotal, int desde, int cada, int idAlias = 0, bool deleteFirst = true)
        {
            Type t = typeof(T);
            string filepath = @"E:\TESTMAPPER." + t.Name + ".xlsx";
            if (deleteFirst && File.Exists(filepath))
                File.Delete(filepath);

            var file = new FileInfo(filepath);
            var package = new ExcelPackage(file);
            ExcelWorksheet wSheet = package.Workbook.Worksheets.Add("prueba1");

            wSheet.Cells[1, 1].Value = $"MAPPERTEST:  MAP {t.Name} ; Tests: { testsTotal.ToString()}. Desde: { desde.ToString()}. Cada: { cada.ToString()}.";
            wSheet.Cells[2, 1].Value = "ID";
            wSheet.Cells[2, 2].Value = "TYPE";
            wSheet.Cells[2, 3].Value = "OBJECTS CREATED";
            wSheet.Cells[2, 4].Value = "SECONDS";
            wSheet.Cells[2, 5].Value = "MILISECONDS";
            wSheet.Cells[2, 6].Value = "SECONDS/OBJECTS";

            Console.WriteLine("MAPPERTEST:");
            Console.WriteLine($@"    1.- MAP {t.Name}
        tests: {testsTotal.ToString()}.
        desde: {desde.ToString()}.
        cada: {cada.ToString()}.");
            Console.WriteLine();
            Console.WriteLine("    -WAITING-");
            Console.ReadLine();
            Console.WriteLine("------------------");
            Console.WriteLine();

            for (int j = desde, x = 0; j < (testsTotal * cada); j += cada, x++)
            {
                Console.WriteLine("...");
                Console.WriteLine($"    Test Nº {x.ToString()}.");
                Console.WriteLine($"    Nº objetos a crear: {j.ToString()}");
                Console.WriteLine();
                Console.WriteLine("...");

                using (SqlConnection con = new SqlConnection(conString))//(MySqlConnection con = new MySqlConnection(conString))
                {
                    con.Open();
                    var result = con.Query(@"SELECT p.Id,p.Nombre,pif.pruebaI,pif.otro,a.Id aId,a.Acc aAcc,a.Persona aPersona 
FROM Persons p 
RIGHT JOIN Acc a ON p.Id=a.Persona 
RIGHT JOIN pruebaif pif ON p.Id=pif.Persona
WHERE p.Id=1");

                    Stopwatch watch = new Stopwatch();
                    watch.Reset();
                    watch.Start();

                    for (int i = 0; i < j; i++)
                    {
                        T p;
                        MapperStore store = new MapperStore();
                        DapperMapper<T> mapper = (DapperMapper<T>)store.GetMapper(t);
                        p = mapper.Map(result);
                    }

                    watch.Stop();
                    Console.WriteLine($"    +++++++++TEST {t.Name} - {x.ToString()} DONE+++++++++");
                    Console.WriteLine();
                    Console.WriteLine($"    TIME WATCH: {watch.Elapsed.ToString()}");
                    Console.WriteLine($"    TIME WATCH SECONDS: {watch.Elapsed.Seconds.ToString()}");
                    Console.WriteLine($"    TIME WATCH MILISECONDS: {watch.ElapsedMilliseconds.ToString()}");
                    Console.WriteLine();

                    wSheet.Cells[x + 2, 1].Value = x + idAlias;
                    wSheet.Cells[x + 2, 2].Value = t.Name;
                    wSheet.Cells[x + 2, 3].Value = j;
                    wSheet.Cells[x + 2, 4].Value = watch.Elapsed.Seconds;
                    wSheet.Cells[x + 2, 5].Value = watch.ElapsedMilliseconds;
                    wSheet.Cells[x + 2, 6].Formula = "D3/C3";

                    con.Close();
                }
                Console.WriteLine("------------------");
                Console.WriteLine("DB OK");
                Console.WriteLine();
            }

            package.Save();
            Console.WriteLine("    +++++++++ALL TESTS DONE+++++++++");
            Console.WriteLine();
            Console.ReadLine();
        }

PS.: There's a SQL query builder in the same Github link that I made for the same project too, but that's another story, it's just string concatenations using some of the mapper dictionaries, just ignore it.

Edit: Asked by @t3chb0t in the comments I'm putting an example of an object to be mapped:

public class Apunte : IObjModelBase, IOwnerComunidad, IObjWithDLO<ApunteDLO>
{
    public Apunte() { }
    public Apunte(int id, int idComunidad, Asiento asiento, string FacturaId = null)
    {
        this._Id = id;
        this._IdOwnerComunidad = idComunidad;
        this._Asiento = asiento;
        this._Factura = FacturaId;
    }

    #region fields
    private int _Id;
    private int _IdOwnerComunidad;
#pragma warning disable CS0649
    private int _OrdenEnAsiento;
#pragma warning restore CS0649
    private Asiento _Asiento;
    private DebitCredit _DebeHaber;
    private decimal _Importe;
    private string _Factura;
    #endregion

    #region properties
    public int Id { get { return this._Id; } }        
    public int IdOwnerComunidad { get { return this._IdOwnerComunidad; } }
    public int OrdenEnAsiento { get { return this._OrdenEnAsiento; } }
    public Asiento Asiento { get { return this._Asiento; } }
    public string Concepto { get; set; }        
    public DebitCredit DebeHaber
    {
        get { return this._DebeHaber; }
        set
        {
            if (this._DebeHaber != value)
            {                    
                if(!_Asiento.Abierto)
                    throw new CustomException_ObjModels(
                        $"Error cambiando DebeHaber de apunte numero {Id} de asiento numero {Asiento.Id}. Asiento cerrado.");
                this._Asiento.CambiaSaldo(this, value);
                this._DebeHaber = value;
            }
        }
    }        
    public decimal Importe
    {
        get { return this._Importe; }
        set
        {
            if (this._Importe != value)
            {
                if (!_Asiento.Abierto)
                    throw new CustomException_ObjModels(
                        $"Error cambiando DebeHaber de apunte numero {Id} de asiento numero {Asiento.Id}. Asiento cerrado.");
                this._Asiento.CambiaSaldo(this, value);
                this._Importe = value;
            }
        }
    }
    public CuentaMayor Cuenta { get; set; }
    public bool Punteo { get; set; }        
    public string Factura { get { return this._Factura; } }
    #endregion

    #region DLO
    public ApunteDLO GetDLO()
    {
        return new ApunteDLO(Id, IdOwnerComunidad, OrdenEnAsiento, Asiento.Codigo.CurrentCodigo, Concepto, DebeHaber, Importe, Cuenta.NumCuenta.ToString(),
            Punteo, Factura);
    }
    #endregion
}

It's a model I use in the same project I did the mapper for. The database have all the plain data, at the beginning of the APP I configure the type (if you note it, it's the same object I used as an example for the configuration), and the repository map the objects through this mapper when needed, again as I described before in the question.

\$\endgroup\$
10
  • 1
    \$\begingroup\$ You write that the other frameworks can't do certain things that you needed... could you name them? What can your mapper do that the other don't? What was the exact reason for writing it? Or did I miss this part? \$\endgroup\$
    – t3chb0t
    Jun 25, 2017 at 18:35
  • \$\begingroup\$ Yes, I wrote that I couldn't make Automapper work with Dapper (probably my fault), but the others just didn't fit my objects or directly what I want. F.i. I don't know how would I map a property setted as an interface with Contrib or Rainbow, specially if it can be mapped to various objects, instead I did a way to define the type to be mapped by certain conditions the dynamic object have to fulfill. They just didn't fit completely what I want, or I just don't know it... \$\endgroup\$
    – Nox
    Jun 25, 2017 at 18:42
  • \$\begingroup\$ Ok, one more last (?) question... why do people need a mapper like this one all? I can imagine mapping a property A from one object o another A property in another object but your mappings (and Automapper) are so crazy and complex that it looks as if your were trying to translate english to chinese ;-) Wouldn't it be easier to write a View that gives you the desired results without mapping? I see a mapper but I cannot establish a connection beween the things you are mapping from and to. Could you add some example models? \$\endgroup\$
    – t3chb0t
    Jun 25, 2017 at 18:47
  • \$\begingroup\$ Well, I can't answer that question for the rest of the people, therefore: I need it because I have like 40 different objects in a project of mine with WPF and C# (that's only the models, not VM and such), I need a way to map plain database data to that objects and it's a huge pain to map every object one by one every time I need a model in the code. So, I did a mapper, one mapper instead of hundreds of manual mappings. \$\endgroup\$
    – Nox
    Jun 25, 2017 at 18:51
  • \$\begingroup\$ Yes, of course I can add an example, going to it ;) \$\endgroup\$
    – Nox
    Jun 25, 2017 at 18:51

2 Answers 2

7
+200
\$\begingroup\$

Please use braces; blocks without braces are just evil. Also, VS 2017 offers to clean these up for you, so it won't be extra work to change your style:

if (!_mtInfos.ContainsKey(t)) _mtInfos.Add(t, changes);
else _mtInfos[t] = changes;

Surely you have some concept of what a T is:

MapperTest<T>

Please rename T to be more descriptive of what it represents.


Please use full names. I had to look for the definition to understand what fInfos represented. What's wrong with fieldInfos?


You don't need the @ on strings that do not span multiple lines.


You might find it useful to use var instead of the full type`.


SetMembersInformation(Type t) is way too long. Cut that into smaller methods.


I may be missing something here, but it looks like preMTInfos contains way too much information. This the Select where you populate it:

.Select(x =>
{
    if (_Ignores.ContainsKey(t) && _Ignores[t].Contains(x.Name))
        return new KeyValuePair<MemberInfo, MemberTypeInfo>(x, MemberTypeInfo.Ignore);
    else return new KeyValuePair<MemberInfo, MemberTypeInfo>(x, MemberTypeInfo.BuiltIn);
})

Your values are always either MemberTypeInfo.Ignore or MemberTypeInfo.BuiltIn. Now, you have two Wheres when you create preNamesList:

.Where(pInfo => preMTInfos[pInfo] != MemberTypeInfo.IEnumerable)

I don't think those will ever return any results because you only have Ignore and BuiltIn.

Later, you filter out the .Ignore ones for your loops. Just don't include those in the first place, because they are never used anyway.


Don't use regions, just organize your files nicely, and you'll know where to find things. I like fields at the top, then ctors, properties, and methods.


I'm sure there are plenty of other things to comment on, but I'm too lazy to write a more in-depth answer at the moment, so I'll leave it for anyone else who wants to answer.

\$\endgroup\$
6
  • \$\begingroup\$ First, I note down the style comments :) I fill preMTInfos only with that two values because I want the keys with no other value not to be empty, but with one of that two values. Later when it map the object, the members that weren't configurated are always built-in, and the mapper will map them that way. \$\endgroup\$
    – Nox
    Jun 25, 2017 at 19:11
  • \$\begingroup\$ By the way, I like regions :( , I use them because it's easier to me to just close all regions and open only those that I want to read at any moment. Why shouldn't I use them? Also, there's something I always get confused about... full names or shorten names? First you say full names, but the method is full and is too large. Are there an "unwritten rule" or something like that? \$\endgroup\$
    – Nox
    Jun 25, 2017 at 19:17
  • \$\begingroup\$ If you really like regions, you can use them, but if you collapse all the methods down to their signature (CTRL M, O), you should be able to find what you need quickly. Also, use full names--I never said to shorten the names. I said the method was too long. Make it shorter by abstracting some of the logic out to other methods and calling them. \$\endgroup\$
    – user34073
    Jun 25, 2017 at 19:46
  • \$\begingroup\$ Oh! Right, sorry I misunderstood it (can't imagine how, but I did misunderstood it), and you're right, it's a nightmare to find something in it. I'll try to divide it in shorter methods. \$\endgroup\$
    – Nox
    Jun 25, 2017 at 20:48
  • 3
    \$\begingroup\$ @Nox to answer "Why shouldn't I use regions?": Some developers like them, others think they shouldn't be used because they can easily hide large sections of code. The thought process is that if you feel a need to manage a class' complexity by adding regions to it, you should instead work on extracting smaller classes out of it. I used to really like regions too, but now my classes rarely get to a line count where I feel they add value. \$\endgroup\$
    – RubberDuck
    Jul 2, 2017 at 18:39
5
\$\begingroup\$

in your DapperMApper you have an if statement inside of your foreach loop, but you don't need the first if statement attached to the rest of the if statement,

if (kvp.Value == MemberTypeInfo.Ignore)
                continue;

should be completely separate, because you are going to the next KeyValuePair if you go into this if statement, and wouldn't hit anything else anyway. By making this a completely separate statement you know that this is a statement to skip the KeyValuePair, and you will see this right away and recognize what it is when reading the code.

This also reduces the amount if statements in this rather large if/else statement.

I also noticed that you have several Commented out lines of code throughout this code.

   //if ((kvp.Value & MemberTypeInfo.Interface) == MemberTypeInfo.Interface) t = ResolveInterface(kvp.Key, dapperResult);
                //else t = GetMemberType(kvp.Key);
                /*
                {
                    //Type of property or field
                    if (kvp.Key.MemberType == MemberTypes.Property) t = ((PropertyInfo)kvp.Key).PropertyType;
                    else t = ((FieldInfo)kvp.Key).FieldType;
                }*/

This is also fairly obvious and doesn't need to be commented on

 //If member is a dictionary
                if (typeof(IDictionary).IsAssignableFrom(t))
\$\endgroup\$
3
  • \$\begingroup\$ I did the if that way because I wanted the members marked with ignore to not go through all the rest of the ifs. It's important that they work in a precise priority order so I just did the same thing with the ignore. But I'm conscious that it's another more if for the rest of the members that aren't marked with ignore. Do you think I should separate it, or should I just remove it? As you said it wouldn't hit anything else anyway, I assured it at the MapperConfig step. \$\endgroup\$
    – Nox
    Jun 26, 2017 at 7:04
  • 1
    \$\begingroup\$ it's an escape clause, it should come first so that the rest of the checks are not hit. but it doesn't need to be attached to the larger if/then block. it is more of a cleanliness thing, it will work either way. Just keep in mind that keeping it separate is the accepted way to do it. \$\endgroup\$
    – Malachi
    Jul 11, 2017 at 12:54
  • \$\begingroup\$ Oh, ok, didn't know that, I'll change it. \$\endgroup\$
    – Nox
    Jul 11, 2017 at 17:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.