【问题标题】:Custom Mapping with AutoMapper使用 AutoMapper 自定义映射
【发布时间】:2015-10-20 19:16:03
【问题描述】:

我有两个非常简单的对象:

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”,它被映射(通过字典)到“我的价值”。

这可以使用 AutoMapper 吗?我当然需要一个适用于每个对象的解决方案,而不仅仅是 Category/CategoryDto。

【问题讨论】:

  • 为什么需要属性,在第一步中,您可以设置自定义映射并将属性键映射到值。这可能吗?
  • 我想创建一个可以在任何地方使用的通用映射器......然后我可以将任何实体映射到任何 dto 而不需要任何额外的代码......
  • 恕我直言,你让你的实体对它不应该负责的事情负责。视图模型应该定义它应该从哪里获取构建自身所需的数据,而不是相反。

标签: c# .net mapping automapper


【解决方案1】:

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

编辑:请注意,它现在更简单(AutoMapper 5.0+),你可以像我在这篇文章中回答的那样做:How to make AutoMapper truncate strings according to MaxLength attribute?

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

【讨论】:

  • 很高兴您找到了解决方案。您能否扩展您的答案以解释这种方法并提供一个使用示例?
【解决方案2】:

使用 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,我建议您通过让属性解析器根据属性/属性信息确定它应该查看的源对象上的属性来清理它,这样您就可以传递一个干净的将新的 PropertyBasedResolver(property) 放入 ResolveUsing() 但希望这足以解释让您走上正轨。

【讨论】:

  • 感谢您的回答。实际上,Value 属性在编译时是未知的。我必须在MapTo 属性的帮助下发现它,所以这个解决方案不起作用......
  • 我不明白。您是说属性 Value 属性本身是未知的(因为您根本没有 dto 的概念)?问题是如何将动态 dto 映射到已知模型?
  • 我更新了问题,也许现在更清楚了。在示例中,Id 映射到 Id(默认映射),Key 应该映射到 MyValueProperty,因为它具有指向“MyValueProperty”的MapTo 属性。模型的值本身是“MyKey”,因此 Dto 中的值将是“MyValue”,因为它的映射方式与字典中的一样。是不是更清楚一点?
  • 我很欣赏第二个代码块中复杂的多运算符语句:)
【解决方案3】:

假设我有以下课程

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


 ));

【讨论】:

  • 不,你不能那样做...Value 未知,我必须处理通用 TSource 和 Tdestination...否则,我必须为所有一对 DtoEntity (在一个大项目中,可能有数百个!)。用我的解决方案,我只做一次......
猜你喜欢
  • 2017-02-17
  • 1970-01-01
  • 2020-05-20
  • 1970-01-01
  • 1970-01-01
  • 2015-10-27
  • 1970-01-01
  • 1970-01-01
  • 2014-08-05
相关资源
最近更新 更多