2010-08-04 58 views
9

我正在使用AutoMapper,并且希望它根据映射(拼合)的目标属性的名称追溯源属性。如何使用AutoMapper根据拼合属性的名称查找源属性

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

例如:

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

然后

跟踪 “AddressZipCode” 回[Source.Address .ZipCode]

这是什么东西th在AutoMapper可以为我做,还是我需要挖掘到AutoMapper的映射数据?

UPDATE

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

更新2

我已经决定提供一些更多的细节。

当我加载了类型映射,我发现有在这两个源值解析器的隐含邮编映射:

  • 一个AutoMapper.Internal.PropertyGetter是获取地址。
  • a AutoMapper.Internal.PropertyGetter获取ZipCode。

当我有一个明确的映射(有指定的lambda表达式),我觉得没有源值解析器,但是自定义解析:

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

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

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

+0

你在说什么flatenning/unflattening? – Omu 2010-08-04 12:17:51

+0

@欧姆:是的,我的意思是我想解除属性名称,而不是整个对象。我发现你与ValueInjecter有关系,但是我希望在这种情况下保持在AutoMapper领域。 – 2010-08-04 12:54:18

+0

好吧,Automapper无法做到这一点,但我在做ValueInjecter解锁时做到了这一点 – Omu 2010-08-04 13:37:43

回答

4

对于目前,我写了一个辅助类,它可以从一个连接的属性链。Ofcourse确定始发产业链,当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); 
+0

但那不是使用automapper? – redsquare 2010-11-02 11:24:54

+0

这是正确的,直到AutoMapper提供了正确的方法(并根据Jimmy - AutoMapper的作者 - 目前没有,我已经更新了答案以反映这一点。) – 2010-11-02 11:26:50

+0

Dang,我真的需要目前,我愿意更改automapper源代码 - 但现在看来这超出了我的想象! – redsquare 2010-11-02 11:29:03

1

我遇到了与AutoMapper类似的需求。这是我可以解决的问题。我只对非常简单的映射进行了测试。主要被用于一类到另一个唯一属性(基本上Mapper.CreateMap的默认行为。我假设只有一个映射,所以我改用遍历集合的第一。

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; 
    } 
+1

谢谢sgriffinusa。你知道的,问题是我的问题的核心是我需要解决一个已经变平的属性。在我的情况下,'destinationPropertyname'的值是'Address.ZipCode',因此将永远不会直接匹配任何源属性。我已更新我的问题以提供更多详细信息。 – 2010-08-07 09:40:26

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>(); 
0

与ValueInjecter你可以这样做:

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

目标是不平坦的对象(我们需要传递的PropertyInfo收集)

路径将是一串字符串列表

var result = string.join(".",trails.ToArray()); 
相关问题