如何使用 AutoMapper 根据扁平化属性的名称查找源属性

2024-04-14

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

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

例如:

[源.地址.邮政编码] 映射到[目的地.地址.邮政编码]

Then

将“地址邮政编码”追溯到 [源.地址.邮政编码]

这是 AutoMapper 可以为我做的事情还是我需要挖掘 AutoMapper 的地图数据?

UPDATE

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

UPDATE 2

我决定提供更多细节。

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

  • a AutoMapper.Internal.PropertyGetter获取地址。
  • a AutoMapper.Internal.PropertyGetter获取邮政编码。

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

  • a AutoMapper.DelegateBasedResolver<Company,string>我认为它包含我的显式映射 lambda 表达式。

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

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


目前,我编写了一个辅助类,可以从串联的属性链中确定原始属性链。当然,当 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);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 AutoMapper 根据扁平化属性的名称查找源属性 的相关文章

随机推荐