2015-07-29 17 views
10

ich zwei sehr einfache Objekte haben:Individuelle Mapping mit AutoMapper

public class CategoryDto 
{ 
    public string Id { get; set; } 

    public string MyValueProperty { get; set; } 
} 

public class Category 
{ 
    public string Id { get; set; } 

    [MapTo("MyValueProperty")] 
    public string Key { get; set; } 
} 

Bei der Zuordnung einer Category zu einem CategoryDto mit AutoMapper, ich möchte folgendes Verhalten:

Die Eigenschaften sollten wie üblich abgebildet werden, außer denen, die das MapTo Attribut haben. In diesem Fall muss ich den Wert des Attributs lesen, um die Zieleigenschaft zu finden. Der Wert der source -Eigenschaft wird verwendet, um den Wert zu finden, der in die Zieleigenschaft (mit Hilfe eines Wörterbuchs) injiziert werden soll. Ein Beispiel dafür ist immer besser, dass 1000 Worte ...

Beispiel:

Dictionary<string, string> keys = 
    new Dictionary<string, string> { { "MyKey", "MyValue" } }; 

Category category = new Category(); 
category.Id = "3"; 
category.Key = "MyKey"; 

CategoryDto result = Map<Category, CategoryDto>(category); 
result.Id    // Expected : "3" 
result.MyValueProperty // Expected : "MyValue" 

Die Key Eigenschaft wird die MyValueProperty (über das Attribut MapTo) abgebildet, und der zugewiesene Wert ist „MyValue“, weil die Quelleigenschaftswert ist "MyKey", der (über ein Wörterbuch) "MyValue" zugeordnet wird.

Ist dies mit AutoMapper möglich? Ich brauche natürlich eine Lösung, die bei jedem Objekt funktioniert, nicht nur bei Category/CategoryDto.

+0

Warum müssen Sie die Attribute Im ersten Schritt können Sie benutzerdefinierte Zuordnungen einrichten und den Eigenschaftsschlüssel auf Wert setzen. Ist das möglich? – whymatter

+0

Ich möchte eine generische Mapper erstellen, die ich überall verwenden kann ... dann könnte ich jede Entität zu jedem dto ohne zusätzlichen Code zuordnen ... – Bidou

+0

Imho Sie machen Ihre Entität verantwortlich für etwas, für das es nicht verantwortlich sein sollte. Das Ansichtsmodell sollte definieren, wo es die Daten erhalten soll, die es benötigt, um sich selbst zu konstruieren, und nicht umgekehrt. – Peter

Antwort

6

Ich endlich (nach so vielen Stunden !!!!) fand eine Lösung. Ich teile das mit der Gemeinschaft; hoffentlich hilft es jemand anderes ...

public static class Extensions 
{ 
    public static IMappingExpression<TSource, TDestination> MapTo<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression) 
    { 
     Type sourceType = typeof(TSource); 
     Type destinationType = typeof(TDestination); 

     TypeMap existingMaps = Mapper.GetAllTypeMaps().First(b => b.SourceType == sourceType && b.DestinationType == destinationType); 
     string[] missingMappings = existingMaps.GetUnmappedPropertyNames(); 

     if (missingMappings.Any()) 
     { 
      PropertyInfo[] sourceProperties = sourceType.GetProperties(); 
      foreach (string property in missingMappings) 
      { 
       foreach (PropertyInfo propertyInfo in sourceProperties) 
       { 
        MapToAttribute attr = propertyInfo.GetCustomAttribute<MapToAttribute>(); 
        if (attr != null && attr.Name == property) 
        { 
         expression.ForMember(property, opt => opt.ResolveUsing(new MyValueResolve(propertyInfo))); 
        } 
       } 
      } 
     } 

     return expression; 
    } 
} 

public class MyValueResolve : IValueResolver 
{ 
    private readonly PropertyInfo pInfo = null; 

    public MyValueResolve(PropertyInfo pInfo) 
    { 
     this.pInfo = pInfo; 
    } 

    public ResolutionResult Resolve(ResolutionResult source) 
    { 
     string key = pInfo.GetValue(source.Value) as string; 
     string value = dictonary[key]; 
     return source.New(value); 
    } 
} 
+5

Froh, dass Sie eine Lösung gefunden haben. Können Sie Ihre Antwort ein wenig erweitern, um die Vorgehensweise zu erklären und ein Anwendungsbeispiel zu geben? – JohnnyHK

0

Dies sollte relativ einfach mit einer Implementierung von IValueResolver und der ResolveUsing() -Methode sein. Sie müssen im Grunde nur einen Konstruktor für den Resolver haben, der die Property-Informationen aufnimmt (oder wenn Sie Lust auf einen Lambda-Ausdruck haben und die Property-Informationen ähnlich wie How to get the PropertyInfo of a specific property? auflösen. Obwohl ich es nicht selbst getestet habe, stelle ich mir das vor folgende funktionieren würde:

public class PropertyBasedResolver : IValueResolver 
{ 
    public PropertyInfo Property { get; set; } 

    public PropertyBasedResolver(PropertyInfo property) 
    { 
      this.Property = property; 
    } 

    public ResolutionResult Resolve(ResolutionResult source) 
    { 
      var result = GetValueFromKey(property, source.Value); // gets from some static cache or dictionary elsewhere in your code by reading the prop info and then using that to look up the value based on the key as appropriate 
      return source.New(result) 
    } 
} 

dann das Mapping Sie tun müssen, einzurichten:

AutoMapper.Mapper.CreateMap<Category, CategoryDto>() 
    .ForMember(
     dest => dest.Value, 
     opt => opt.ResolveUsing(
       src => 
        new PropertyBasedResolver(typeof(Category.Key) as PropertyInfo).Resolve(src))); 

natürlich ist das ein ziemlich eklig lamda ist und ich würde vorschlagen, dass Sie es aufzuräumen, indem Sie Ihre Eigenschaft, resolver bestimmt die Eigenschaft des Quellobjekts, das es anhand des Attributs/der Eigenschafteninformation untersuchen soll, damit Sie ju St übergeben Sie eine saubere neue PropertyBasedResolver (Eigenschaft) in die ResolveUsing(), aber hoffentlich erklärt dies genug, um Sie auf den richtigen Weg zu bringen.

+0

Vielen Dank für Ihre Antwort. Tatsächlich ist die Eigenschaft "Value" zur Kompilierzeit nicht bekannt. Ich muss es mit Hilfe des 'MapTo'-Attributs entdecken, so dass diese Lösung nicht funktioniert ... – Bidou

+0

Ich verstehe nicht.Willst du damit sagen, dass die Eigenschaftswerteigenschaft selbst nicht bekannt ist (wie in dir gibt es überhaupt kein dto-Konzept)? Ist die Frage, wie man ein dynamisches dto einem bekannten Modell zuordnet? –

+0

Ich habe die Frage aktualisiert, vielleicht ist es jetzt ein bisschen klarer. In diesem Beispiel wird "Id" auf "Id" (Standardzuordnung) und "Key" auf "MyValueProperty" abgebildet, da das Attribut "MapTo" auf "MyValueProperty" verweist. Der Wert des Modells ist "MyKey", daher ist der Wert in Dto "MyValue", da er wie im Wörterbuch abgebildet ist. Ist es ein bisschen klarer? – Bidou

0

Nehmen wir an, ich die folgenden Klassen haben

public class foo 
{ 
    public string Value; 
} 
public class bar 
{ 
    public string Value1; 
    public string Value2; 
} 

Sie eine Lambda ResolveUsing passieren kann:

.ForMember(f => f.Value, o => o.ResolveUsing(b => 
{ 
    if (b.Value1.StartsWith("A"));) 
    { 
     return b.Value1; 
    } 
    return b.Value2; 
} 


)); 
+0

Nein, das kannst du nicht tun ... "Wert" ist nicht bekannt, ich muss an einer generischen TSource und TDestination arbeiten ... ansonsten muss ich ein "ForMember" für alle Paare erstellen Dto <--> Entity (in ein großes Projekt, das möglicherweise viele hundert sein könnte!). Mit meiner Lösung mache ich es einfach einmal ... – Bidou

Verwandte Themen