使用 AutoMapper 自定义映射

Custom Mapping with 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; }
}

使用 AutoMapper 将 Category 映射到 CategoryDto 时,我想要以下行为:

属性应照常映射,具有 MapTo 属性的属性除外。在这种情况下,我必须读取 Attribute 的值才能找到目标 属性。源 属性 的值用于查找要注入目标 属性 的值(借助字典)。一个例子总比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.

使用 IValueResolver 的实现和 ResolveUsing() 方法,这应该相当简单。您基本上只需要为接受 属性 信息的解析器构造一个构造函数(或者如果您想接受一个 lambda 表达式并解析类似于 How to get the PropertyInfo of a specific property? 的 属性 信息) .虽然我自己还没有测试过,但我想下面的方法会起作用:

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

当然,这是一个非常糟糕的 lamda,我建议您通过让 属性 解析器根据 属性 确定它应该查看的源对象来清理它=19=] 信息,这样你就可以将一个干净的新 PropertyBasedResolver(属性) 传递到 ResolveUsing() 但希望这足以让你走上正确的轨道。

我终于(经过这么多小时!!!!)找到了解决方案。 我与社区分享这个;希望它能帮助别人...

编辑: 请注意,它现在更简单了(AutoMapper 5.0+),你可以像我在这个 post 中回答的那样做:

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

假设我有以下 类

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

您可以将 lambda 传递给 ResolveUsing:

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


 ));