c# – 展平嵌套对象以将其属性映射到目标对象

我正在尝试使用AutoMapper来映射这样的类:

class FooDTO
{
    public int X { get; set; }
    public EmbeddedDTO Embedded { get; set; }
    public class EmbeddedDTO
    {
        public BarDTO Y { get; set; }
        public BazDTO Z { get; set; }
    }
}

对于这样的类:

class Foo
{
    public int X { get; set; }
    public Bar Y { get; set; }
    public Baz Z { get; set; }
}

(FooDTO是一个HAL资源)

我知道我可以通过这样明确地创建地图来实现:

Mapper.CreateMap<FooDTO,Foo>()
      .ForMember(f => f.Y,c => c.MapFrom(f => f.Embedded.Y))
      .ForMember(f => f.Z,c => c.MapFrom(f => f.Embedded.Z));

或者甚至用这样的技巧:

Mapper.CreateMap<FooDTO,Foo>()
      .AfterMap((source,dest) => Mapper.Map(source.Embedded,dest));

但问题是我将有许多类似的HAL资源来映射,而我宁愿不必单独配置每个资源.我实际上有一个通用对象模型,如下所示:

class HalResource
{
    [JsonProperty("_links")]
    public IDictionary<string,HalLink> Links { get; set; }
}

class HalResource<TEmbedded> : HalResource
{
    [JsonProperty("_embedded")]
    public TEmbedded Embedded { get; set; }
}

class HalLink
{
    [JsonProperty("href")]
    public string Href { get; set; }
}

使用此模型,FooDTO类实际上是这样声明的

class FooDTO : HalResource<FooDTO.EmbeddedDTO>
{
    public int X { get; set; }
    public class EmbeddedDTO
    {
        public int Y { get; set; }
        public int Z { get; set; }
    }
}

有没有办法为继承HalResource< TEmbedded>的所有类全局配置映射,以便DTO的Embedded属性属性直接映射到目标对象?我尝试用自定义的IObjectMapper来做,但事实证明它比我预期的更具挑战性……

解决方法

如果您的用例与问题中的限制一样,那就是:

>从HalResource派生实例到直接POCOS的单向映射(与双向映射相比)
>映射相同名称和类型的属性
>您在此处介绍的确切嵌入式结构

考虑到这种结构,自己设置一个特定的映射可能是有意义的.如果我对一些明确的映射约定(而不是依赖于诸如AutoMapper的通用映射器)的映射需求非常狭窄,那么我倾向于这样做.为此,我有一些构建块,我倾向于在不同的上下文中重用.我将一个映射器组合在一起,适用于您从这些构建块中描述的问题,如下所示:

public class Mapper
{
    private const BindingFlags DestConstructorFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
    private const BindingFlags DestFlags = BindingFlags.Instance | BindingFlags.Public;
    private const BindingFlags SrcFlags = BindingFlags.Instance | BindingFlags.Public;
    private static readonly object[] NoArgs = new object[0];
    private static readonly Type GenericEmbeddedSourceType = typeof(HalResource<>);
    private readonly Dictionary<Type,Func<object,object>> _oneWayMap = new Dictionary<Type,object>>();

    public void CreateMap<TDestination,TSource>() 
        where TDestination : class 
        where TSource : HalResource
    {
        CreateMap(typeof(TDestination),typeof(TSource));
    }

    public void CreateMap(Type destType,Type srcType)
    {
        _oneWayMap[srcType] = InternalCreateMapper(destType,srcType);
    }

    public object Map<TSource>(TSource toMap) where TSource : HalResource
    {
        var mapper = default(Func<object,object>);
        if (!_oneWayMap.TryGetValue(typeof(TSource),out mapper))
            throw new KeyNotFoundException(string.Format("No mapping for {0} is defined.",typeof(TSource)));
        return mapper(toMap);
    }

    public TDestination Map<TDestination,TSource>(TSource toMap)
        where TDestination : class
        where TSource : HalResource
    {
        var converted = Map(toMap);
        if (converted != null && !typeof(TDestination).IsAssignableFrom(converted.GetType()))
            throw new InvalidOperationException(string.Format("No mapping from type {0} to type {1} has been configured.",typeof(TSource),typeof(TDestination)));
        return (TDestination)converted;
    }

    public void Clear()
    {
        _oneWayMap.Clear();
    }

    private static Func<object,object> InternalCreateMapper(Type destType,Type srcType)
    {
        // Destination specific constructor + setter map.
        var destConstructor = BuildConstructor(destType.GetConstructor(DestConstructorFlags,null,Type.EmptyTypes,null));
        var destSetters = destType
            .GetProperties(DestFlags)
            .Where(p => p.CanWrite)
            .ToDictionary(k => k.Name,v => Tuple.Create(v.PropertyType,BuildSetter(v)));

        // Source specific getter maps
        var srcPrimPropGetters = CreateGetters(srcType);
        var srcEmbeddedGetter = default(Func<object,object>);
        var srcEmbeddedPropGetters = default(IDictionary<string,Tuple<Type,object>>>);
        var baseType = srcType.BaseType;
        while (baseType != null && baseType != typeof(object))
        {
            if (baseType.IsGenericType && GenericEmbeddedSourceType.IsAssignableFrom(baseType.GetGenericTypeDeFinition()))
            {
                var genericParamType = baseType.GetGenericArguments()[0];
                if (srcPrimPropGetters.Any(g => g.Value.Item1.Equals(genericParamType)))
                {
                    var entry = srcPrimPropGetters.First(g => g.Value.Item1.Equals(genericParamType));
                    srcPrimPropGetters.Remove(entry.Key);
                    srcEmbeddedGetter = entry.Value.Item2;
                    srcEmbeddedPropGetters = CreateGetters(entry.Value.Item1);
                    break;
                }
            }
            baseType = baseType.BaseType;
        }

        // Build mapper delegate function.
        return (src) =>
        {
            var result = destConstructor(NoArgs);
            var srcEmbedded = srcEmbeddedGetter != null ? srcEmbeddedGetter(src) : null;
            foreach (var setter in destSetters)
            {
                var getter = default(Tuple<Type,object>>);
                if (srcPrimPropGetters.TryGetValue(setter.Key,out getter) && setter.Value.Item1.IsAssignableFrom(getter.Item1))
                    setter.Value.Item2(result,getter.Item2(src));
                else if (srcEmbeddedPropGetters.TryGetValue(setter.Key,getter.Item2(srcEmbedded));
            }
            return result;
        };
    }

    private static IDictionary<string,object>>> CreateGetters(Type srcType)
    {
        return srcType
            .GetProperties(SrcFlags)
            .Where(p => p.CanRead)
            .ToDictionary(k => k.Name,BuildGetter(v)));
    }

    private static Func<object[],object> BuildConstructor(ConstructorInfo constructorInfo)
    {
        var param = Expression.Parameter(typeof(object[]),"args");
        var argsExp = constructorInfo.GetParameters()
            .Select((p,i) => Expression.Convert(Expression.ArrayIndex(param,Expression.Constant(i)),p.ParameterType))
            .ToArray();
        return Expression.Lambda<Func<object[],object>>(Expression.New(constructorInfo,argsExp),param).Compile();
    }

    private static Func<object,object> BuildGetter(PropertyInfo propertyInfo)
    {
        var instance = Expression.Parameter(typeof(object),"instance");
        var instanceCast = propertyInfo.DeclaringType.IsValueType
            ? Expression.Convert(instance,propertyInfo.DeclaringType)
            : Expression.TypeAs(instance,propertyInfo.DeclaringType);
        var propertyCast = Expression.TypeAs(Expression.Property(instanceCast,propertyInfo),typeof(object));
        return Expression.Lambda<Func<object,object>>(propertyCast,instance).Compile();
    }

    private static Action<object,object> BuildSetter(PropertyInfo propertyInfo)
    {
        var setMethodInfo = propertyInfo.GetSetMethod(true);
        var instance = Expression.Parameter(typeof(object),"instance");
        var value = Expression.Parameter(typeof(object),"value");
        var instanceCast = propertyInfo.DeclaringType.IsValueType
            ? Expression.Convert(instance,propertyInfo.DeclaringType);
        var call = Expression.Call(instanceCast,setMethodInfo,Expression.Convert(value,propertyInfo.PropertyType));
        return Expression.Lambda<Action<object,object>>(call,instance,value).Compile();
    }
}

可以执行一些优化,但性能可能足以解决大多数问题.然后可以使用它:

public abstract class HalResource
{
    public IDictionary<string,HalLink> Links { get; set; }
}

public abstract class HalResource<TEmbedded> : HalResource
{
    public TEmbedded Embedded { get; set; }
}

public class HalLink
{
    public string Href { get; set; }
}

public class FooDTO : HalResource<FooDTO.EmbeddedDTO>
{
    public int X { get; set; }
    public class EmbeddedDTO
    {
        public int Y { get; set; }
        public int Z { get; set; }
    }
}

public class MyMappedFoo
{
    public int X { get; set; }
    public int Y { get; set; }
    public int Z { get; set; }
}

class Program
{
    public static void Main(params string[] args)
    {
        // Configure mapper manually
        var mapper = new Mapper();
        mapper.CreateMap<MyMappedFoo,FooDTO>();

        var myDTO = new FooDTO 
        { 
            X = 10,Embedded = new FooDTO.EmbeddedDTO { Y = 5,Z = 9 } 
        };
        var mappedFoo = mapper.Map<MyMappedFoo,FooDTO>(myDTO);
        Console.WriteLine("X = {0},Y = {1},Z = {2}",mappedFoo.X,mappedFoo.Y,mappedFoo.Z);

        Console.WriteLine("Done");
        Console.ReadLine();
    }
}

如果可以通过约定发现源和目标类型,则可以更进一步,并使用编码这些约定的构建器填充映射,如下面的示例所示(同样不是最佳实现,但有说明点):

public static class ByConventionMapBuilder
{
    public static Func<IEnumerable<Type>> DestinationTypesProvider = DefaultDestTypesProvider;
    public static Func<IEnumerable<Type>> SourceTypesProvider = DefaultSourceTypesProvider;
    public static Func<Type,Type,bool> TypeMatcher = DefaultTypeMatcher;

    public static Mapper Build()
    {
        var mapper = new Mapper();
        var sourceTypes = SourceTypesProvider().ToList();
        var destTypes = DestinationTypesProvider();
        foreach (var destCandidateType in destTypes)
        {
            var match = sourceTypes.FirstOrDefault(t => TypeMatcher(t,destCandidateType));
            if (match != null)
            {
                mapper.CreateMap(destCandidateType,match);
                sourceTypes.Remove(match);
            }
        }
        return mapper;
    }

    public static IEnumerable<Type> TypesFromAssembliesWhere(Func<IEnumerable<Assembly>> assembliesProvider,Predicate<Type> matches)
    {
        foreach (var a in assembliesProvider())
        {
            foreach (var t in a.GetTypes())
            {
                if (matches(t))
                    yield return t;
            }
        }
    }

    private static IEnumerable<Type> DefaultDestTypesProvider()
    {
        return TypesFromAssembliesWhere(
            () => new[] { Assembly.GetExecutingAssembly() },t => t.IsClass && !t.IsAbstract && !t.Name.EndsWith("DTO"));
    }

    private static IEnumerable<Type> DefaultSourceTypesProvider()
    {
        return TypesFromAssembliesWhere(
            () => new[] { Assembly.GetExecutingAssembly() },t => typeof(HalResource).IsAssignableFrom(t) && !t.IsAbstract && t.Name.EndsWith("DTO"));
    }

    private static bool DefaultTypeMatcher(Type srcType,Type destType)
    {
        var stn = srcType.Name;
        return (stn.Length > 3 && stn.EndsWith("DTO") && destType.Name.EndsWith(stn.Substring(0,stn.Length - 3)));
    }
}

class Program
{
    public static void Main(params string[] args)
    {
        // Configure mapper by type scanning & convention matching
        var mapper = ByConventionMapBuilder.Build();

        var myDTO = new FooDTO 
        { 
            X = 10,mappedFoo.Z);

        Console.WriteLine("Done");
        Console.ReadLine();
    }
}

如果您有其他理由想要继续使用AutoMapper,我建议创建一个类似的地图构建器,它对类型匹配和嵌入属性映射进行编码.

相关文章

目录简介使用JS互操作使用ClipLazor库创建项目使用方法简单测...
目录简介快速入门安装 NuGet 包实体类User数据库类DbFactory...
本文实现一个简单的配置类,原理比较简单,适用于一些小型项...
C#中Description特性主要用于枚举和属性,方法比较简单,记录...
[TOC] # 原理简介 本文参考[C#/WPF/WinForm/程序实现软件开机...
目录简介获取 HTML 文档解析 HTML 文档测试补充:使用 CSS 选...