代码之家  ›  专栏  ›  技术社区  ›  Sandor Drieënhuizen

如何使用AutoMapper根据展开属性的名称查找源属性

  •  9
  • Sandor Drieënhuizen  · 技术社区  · 14 年前

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

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

    例如:

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

    那么

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

    更新2

    我决定提供更多的细节。

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

    • AutoMapper.Internal.PropertyGetter 那就是地址。
    • AutoMapper.Internal.PropertyGetter公司

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

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

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

    4 回复  |  直到 14 年前
        1
  •  4
  •   Sandor Drieënhuizen    13 年前

    目前,我编写了一个helper类,它可以从连接的属性链中确定原始属性链。当然,当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);
    
        2
  •  1
  •   sgriffinusa    14 年前

    我在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;
        }
    
        3
  •  1
  •   Simon Bartlett    14 年前

    我一直在研究同样的问题,并提出了以下代码片段。它从AutoMapper提出了一个没有平台的产业链。我从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());
        }
    }
    

    您可以使用以下方法获得所需的类型映射:

    Mapper.FindTypeMapFor<TSource, TDestination>();
    
        4
  •  0
  •   Omu    12 年前

    使用ValueInjector,您可以:

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

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

    轨迹将是一个字符串列表,因此

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