Skip to content

Nested mappings

StandBackBurrito edited this page Oct 17, 2014 · 10 revisions

As the mapping engine executes the mapping, it can use one of a variety of methods to resolve a destination member value. One of these methods is to use another type map, where the source member type and destination member type are also configured in the mapping configuration. This allows us to not only flatten our source types, but create complex destination types as well. For example, our source type might contain another complex type:

    public class OuterSource
    {
    	public int Value { get; set; }
    	public InnerSource Inner { get; set; }
    }
    
    public class InnerSource
    {
    	public int OtherValue { get; set; }
    }

We could simply flatten the OuterSource.Inner.OtherValue to one InnerOtherValue property, but we might also want to create a corresponding complex type for the Inner property:

    public class OuterDest
    {
    	public int Value { get; set; }
    	public InnerDest Inner { get; set; }
    }
    
    public class InnerDest
    {
    	public int OtherValue { get; set; }
    }

In that case, we would need to configure the additional source/destination type mappings:

    Mapper.CreateMap<OuterSource, OuterDest>();
    Mapper.CreateMap<InnerSource, InnerDest>();
    Mapper.AssertConfigurationIsValid();
    
    var source = new OuterSource
    	{
    		Value = 5,
    		Inner = new InnerSource {OtherValue = 15}
    	};
    
    var dest = Mapper.Map<OuterSource, OuterDest>(source);
    
    dest.Value.ShouldEqual(5);
    dest.Inner.ShouldNotBeNull();
    dest.Inner.OtherValue.ShouldEqual(15);

Maybe we want to be free from creating so many nested mappings,Here is a simple way to resolve:

        /// <summary>
        /// 递归创建类型间的映射关系 (Recursively create mappings between types)
        ///created by cqwang
        /// </summary>
        /// <param name="sourceType"></param>
        /// <param name="destinationType"></param>
        public static void CreateNestedMappers(Type sourceType, Type destinationType)
        {
            PropertyInfo[] sourceProperties = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            PropertyInfo[] destinationProperties = destinationType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (var destinationProperty in destinationProperties)
            {
                Type destinationPropertyType = destinationProperty.PropertyType;
                if (Filter(destinationPropertyType))
                    continue;

                PropertyInfo sourceProperty = sourceProperties.FirstOrDefault(prop => NameMatches(prop.Name, destinationProperty.Name));
                if (sourceProperty == null)
                    continue;

                Type sourcePropertyType=sourceProperty.PropertyType;
                if (destinationPropertyType.IsGenericType)
                {
                    Type destinationGenericType = destinationPropertyType.GetGenericArguments()[0];
                    if (Filter(destinationGenericType))
                        continue;

                    Type sourceGenericType = sourcePropertyType.GetGenericArguments()[0];
                    CreateMappers(sourceGenericType, destinationGenericType);
                }
                else
                {
                    CreateMappers(sourcePropertyType, destinationPropertyType);
                }
            }

            Mapper.CreateMap(sourceType, destinationType);
        }

        /// <summary>
        /// 过滤 (Filter)
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        static bool Filter(Type type)
        {
            return type.IsPrimitive || NoPrimitiveTypes.Contains(type.Name);
        }

        static readonly HashSet<string> NoPrimitiveTypes = new HashSet<string>() { "String", "DateTime", "Decimal" };

        private static bool NameMatches(string memberName, string nameToMatch)
        {
            return String.Compare(memberName, nameToMatch, StringComparison.OrdinalIgnoreCase) == 0;
        }

A few things to note here:

  • Order of configuring types does not matter
  • Call to Map does not need to specify any inner type mappings, only the type map to use for the source value passed in

With both flattening and nested mappings, we can create a variety of destination shapes to suit whatever our needs may be.

Clone this wiki locally