web-dev-qa-db-ja.com

AutoMapperによるカスタムマッピング

2つの非常に単純なオブジェクトがあります。

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

AutoMapperを使用してCategoryCategoryDtoにマッピングする場合、次の動作が必要です。

プロパティは、MapTo属性を持つプロパティを除いて、通常どおりマップする必要があります。この場合、ターゲットプロパティを見つけるために属性の値を読み取る必要があります。ソースプロパティの値は、(ディクショナリを使用して)デスティネーションプロパティに注入する値を見つけるために使用されます。例は常に1000語より優れています...

例:

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"

KeyプロパティはMyValuePropertyにマップされ(MapTo属性を介して)、割り当てられた値は "MyValue"です。これは、ソースプロパティ値が "MyKey"であるため、 (ディクショナリを介して)「MyValue」にマッピングされます。

これはAutoMapperを使用して可能ですか?もちろん、Category/CategoryDtoだけでなく、すべてのオブジェクトで機能するソリューションが必要です。

13
Bidou

私はようやく(非常に多くの時間の後!!!!)解決策を見つけました。これをコミュニティと共有します。うまくいけば、それは他の誰かを助けるでしょう...

編集:かなりシンプルになりました(AutoMapper 5.0+)。この投稿で私が回答したように、次のことができます: AutoMapperの作成方法MaxLength属性に従って文字列を切り捨てますか?

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);
    }
}
10
Bidou

これは、IValueResolverとResolveUsing()メソッドの実装を使用すると、かなり簡単になるはずです。基本的には、プロパティ情報を取り込むリゾルバーのコンストラクターが必要です(または、ラムダ式を取り込んでプロパティ情報を解決する空想になりたい場合は 特定のプロパティのPropertyInfoを取得する方法? 。私は自分でテストしていませんが、次のように機能すると思います。

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)
     }
}

次に、そのマッピングを設定するには、次のことを行う必要があります。

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

もちろん、それはかなり大きなラムダであり、プロパティリゾルバに、属性/プロパティ情報に基づいて参照するソースオブジェクトのプロパティを決定させ、クリーンな新しいPropertyBasedResolver(プロパティ)をResolveUsing()に入れますが、うまくいけば、これで十分に説明できます。

0
Daniel King

次のクラスがあるとしましょう

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

ラムダをResolveUsingに渡すことができます:

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


 ));
0
Naimish Mungara