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

San*_*zen 9 .net c# automapper

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

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

例如:

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

然后

将"AddressZipCode"追溯回[Source.Address.ZipCode]

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

UPDATE

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

更新2

我决定提供更多细节.

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

  • 一个AutoMapper.Internal.PropertyGetter是获取地址.
  • 一个AutoMapper.Internal.PropertyGetter是可以获得邮编.

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

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

不幸的是,这些解析器是内部的,所以我只能通过反射(我真的不想这样做),或者通过改变AutoMapper源代码访问它们.

如果我能访问他们,我可以解决了既可以通过值解析器步行或通过检查自定义解析器但我怀疑它的问题,这将导致我回映射lambda表达式,我需要建立不平属性名(实际上是由点分隔的一系列属性名称).

San*_*zen 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个使用示例:

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);
Run Code Online (Sandbox Code Playgroud)