如何使用AutoMapper基于展平属性的名称查找源属性

时间:2010-08-04 11:44:29

标签: c# .net automapper

我正在使用AutoMapper,我希望它根据映射(展平的)目标属性的名称追溯源属性。

这是因为我的MVC控制器具有映射属性的名称,它需要提供给服务调用以进行排序。服务需要知道映射源自的属性的名称(并且控制器不应该知道它),以便对实际对数据进行排序的存储库执行适当的调用。

例如:

  

[Source.Address.ZipCode]映射到[Destination.AddressZipCode]

然后

  

将“AddressZipCode”追溯回[Source.Address.ZipCode]

这是AutoMapper可以为我做的事情还是我需要求助于挖掘AutoMapper的映射数据?

更新

Jimmy Bogard告诉我,这应该是可能的,但不是以明显的方式。它需要加载类型映射并通过它。我已经简要地研究了一下,但似乎我需要访问内部类型来获取进行反向映射所需的属性映射信息。

更新2

我决定提供更多细节。

当我加载类型映射时,我发现其中有两个源值解析器用于隐式ZipCode映射:

  • 获得地址的AutoMapper.Internal.PropertyGetter
  • 获得ZipCode的AutoMapper.Internal.PropertyGetter

当我有一个显式映射(指定了lambda表达式)时,我找不到源值解析器而是自定义解析器:

  • 我认为AutoMapper.DelegateBasedResolver<Company,string>拥有我的显式映射lambda表达式。

不幸的是这些解析器是内部的,因此我只能通过反射(我真的不想这样做)或通过更改AutoMapper源代码来访问它们。

如果我可以访问它们,我可以通过遍历值解析器或检查自定义解析器来解决问题,尽管我怀疑它会导致我回到映射lambda表达式,我需要构建unflattened属性名称(实际上是一系列由点分隔的属性名称。)

4 个答案:

答案 0 :(得分:4)

目前,我编写了一个帮助类,可以从连锁的属性链中确定原始属性链。当然,当AutoMapper获得执行此类操作的功能时,这将变得过时。

using System.Globalization;
using System.Reflection;

/// <summary>
///     Resolves concatenated property names back to their originating properties.
/// </summary>
/// <remarks>
///     An example of a concatenated property name is "ProductNameLength" where the originating
///     property would be "Product.Name.Length".
/// </remarks>
public static class ConcatenatedPropertyNameResolver
{
    private static readonly object mappingCacheLock = new object();
    private static readonly Dictionary<MappingCacheKey, string> mappingCache = new Dictionary<MappingCacheKey, string>();

    /// <summary>
    ///     Returns the nested name of the property the specified concatenated property
    ///     originates from.
    /// </summary>
    /// <param name="concatenatedPropertyName">The concatenated property name.</param>
    /// <typeparam name="TSource">The mapping source type.</typeparam>
    /// <typeparam name="TDestination">The mapping destination type.</typeparam>
    /// <returns>
    ///     The nested name of the originating property where each level is separated by a dot.
    /// </returns>
    public static string GetOriginatingPropertyName<TSource, TDestination>(string concatenatedPropertyName)
    {
        if (concatenatedPropertyName == null)
        {
            throw new ArgumentNullException("concatenatedPropertyName");
        }
        else if (concatenatedPropertyName.Length == 0)
        {
            throw new ArgumentException("Cannot be empty.", "concatenatedPropertyName");
        }

        lock (mappingCacheLock)
        {
            MappingCacheKey key = new MappingCacheKey(typeof(TSource), typeof(TDestination), concatenatedPropertyName);

            if (!mappingCache.ContainsKey(key))
            {
                BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public;

                List<string> result = new List<string>();
                Type type = typeof(TSource);

                while (concatenatedPropertyName.Length > 0)
                {
                    IEnumerable<PropertyInfo> properties = type.GetProperties(bindingFlags).Where(
                        n => concatenatedPropertyName.StartsWith(n.Name)).ToList();

                    if (properties.Count() == 1)
                    {
                        string match = properties.First().Name;
                        result.Add(match);
                        concatenatedPropertyName = concatenatedPropertyName.Substring(match.Length);
                        type = type.GetProperty(match, bindingFlags).PropertyType;
                    }
                    else if (properties.Any())
                    {
                        throw new InvalidOperationException(
                            string.Format(
                                CultureInfo.InvariantCulture,
                                "Ambiguous properties found for {0} on type {1}: {2}.",
                                concatenatedPropertyName,
                                typeof(TSource).FullName,
                                string.Join(", ", properties.Select(n => n.Name))));
                    }
                    else
                    {
                        throw new InvalidOperationException(
                            string.Format(
                                CultureInfo.InvariantCulture,
                                "No matching property found for {0} on type {1}.",
                                concatenatedPropertyName,
                                typeof(TSource).FullName));
                    }
                }

                mappingCache.Add(key, string.Join(".", result));
            }

            return mappingCache[key];
        }
    }

    /// <summary>
    ///     A mapping cache key.
    /// </summary>
    private struct MappingCacheKey
    {
        /// <summary>
        ///     The source type.
        /// </summary>
        public Type SourceType;

        /// <summary>
        ///     The destination type the source type maps to. 
        /// </summary>
        public Type DestinationType;

        /// <summary>
        ///     The name of the mapped property.
        /// </summary>
        public string MappedPropertyName;

        /// <summary>
        ///     Initializes a new instance of the <see cref="MappingCacheKey"/> class.
        /// </summary>
        /// <param name="sourceType">The source type.</param>
        /// <param name="destinationType">The destination type the source type maps to.</param>
        /// <param name="mappedPropertyName">The name of the mapped property.</param>
        public MappingCacheKey(Type sourceType, Type destinationType, string mappedPropertyName)
        {
            SourceType = sourceType;
            DestinationType = destinationType;
            MappedPropertyName = mappedPropertyName;
        }
    }
}

这是一个用法示例:

class TestEntity
{
    public Node Root {get; set;}
}

class Node
{
    public string Leaf {get; set;}
}

class TestFlattenedEntity
{
    public string RootLeaf {get; set;}
}

string result = ConcatenatedPropertyNameResolver.GetOriginatingPropertyName<TestEntity, TestFlattenedEntity>("RootLeaf");

Assert.AreEqual("Root.Leaf", result);

答案 1 :(得分:1)

我遇到了与AutoMapper类似的需求。这是我能解决的解决方案。我只针对非常简单的映射进行了测试。主要是一个类到另一个只使用属性的类(基本上是Mapper.CreateMap的默认行为。我假设只有一个映射,所以我使用First而不是迭代集合。

    private MemberInfo getSource(Type destinationType, string destinationPropertyname)
    {
        TypeMap map = Mapper.GetAllTypeMaps().Where(m => m.DestinationType.Equals(destinationType)).First();

        IEnumerable<PropertyMap> properties = map.GetPropertyMaps().Where(p => p.DestinationProperty.Name.Equals(destinationPropertyname, StringComparison.CurrentCultureIgnoreCase));

        PropertyMap sourceProperty = properties.First();

        IMemberGetter mg = sourceProperty.GetSourceValueResolvers().Cast<IMemberGetter>().First();

        return mg.MemberInfo;
    }

答案 2 :(得分:1)

我一直在研究同样的问题,并提出了以下代码片段。它提供了来自AutoMapper的unflattened属性链。我从sgriffinusa的解决方案中获得了一些灵感。

using System.Linq;
using System.Reflection;
using AutoMapper;

public static class TypeMapExtensions
{
    public static MemberInfo[] TryGetSourceProperties(this TypeMap @this, string propertyName)
    {
        if (@this != null)
        {
            var propertyMap = @this.GetPropertyMaps()
                .Where(p => p.DestinationProperty.Name == propertyName).FirstOrDefault();

            if (propertyMap != null)
            {
                var sourceProperties = propertyMap.GetSourceValueResolvers().OfType<IMemberGetter>();
                if (sourceProperties.Any())
                    return sourceProperties.Select(x => x.MemberInfo).ToArray();
            }
        }
        return null;
    }

    /// <summary>
    /// Trys to retrieve a source property name, given a destination property name. Only handles simple property mappings, and flattened properties.
    /// </summary>
    public static string TryGetSourcePropertyName(this TypeMap @this, string propertyName)
    {
        var members = TryGetSourceProperties(@this, propertyName);
        return (members == null) ? null : string.Join(".", members.Select(x => x.Name).ToArray());
    }
}

使用以下方法获得所需的TypeMap:

Mapper.FindTypeMapFor<TSource, TDestination>();

答案 3 :(得分:0)

你可以使用ValueInjecter:

var trails = TrailFinder.GetTrails(flatPropertyName, target.GetType().GetInfos(), t => true);

target是unflat对象(我们需要传递PropertyInfo的集合)

trail将是一个字符串列表,所以

var result = string.join(".",trails.ToArray());