2010-08-04 60 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()); 
相關問題