【问题标题】:AutoMapper: Deep property mapping + converterAutoMapper:深度属性映射+转换器
【发布时间】:2021-07-27 23:50:50
【问题描述】:

每个端点都有很多自动生成的类似 API 模型(我无法更改!)并且喜欢映射到单个目的地。

例如,如果我想从 Dun&Bradstreet API 映射 IDRCleanseMatch.jsoncmpelkv2.json 端点:

Api1.Organization → OrganizationDestinationModel
Api2.Organization → OrganizationDestinationModel

以下是源模型和目标模型:

namespace Api1
{
    public class Organization 
    {
        public string Duns { get; set; }
        public DunsControlStatus DunsControlStatus { get; set; }
        public string PrimaryName { get; set; }
        //...
    }
    public class DunsControlStatus 
    {
        public OperatingStatus OperatingStatus { get; set; }
        //...
    }
    public class OperatingStatus 
    {
        public string Description { get; set; }
        public int? DnbCode { get; set; }
        //...
    }
}
namespace Api2 { /* Same as above. */ }
public class OrganizationDestinationModel
{
    public string Duns { get; set; }
    public string Name { get; set; }
    public bool IsOutOfBusiness { get; set; }
}

我的方法是使用通用辅助方法:

config.MapToOrganizationDestinationModel<Api1.Organization>();
config.MapToOrganizationDestinationModel<Api2.Organization>();

private static void MapToOrganizationDestinationModel<TSource>(this IMapperConfigurationExpression config)
{
    config
        .CreateMap<TSource, OrganizationDestinationModel>()
        .ForMember(
            destination => destination.Name,
            options => options.MapFrom(nameof(Api1.Organization.PrimaryName))
        )
        // Doesn't work!!!
        // ArgumentException : 'DunsControlStatus.OperatingStatus.DnbCode' is not a member of type 'Core.Api1.Organization' (Parameter 'propertyOrFieldName')
        .ForMember(
            destination => destination.IsOutOfBusiness,
            options => options.ConvertUsing(
                new IsOutOfBusinessConverter(),
                $"{nameof(Api1.Organization.DunsControlStatus)}.{nameof(Api1.DunsControlStatus.OperatingStatus)}.{nameof(Api1.OperatingStatus.DnbCode)}"
            )
        )
        ;
}
public class IsOutOfBusinessConverter : IValueConverter<int?, bool>
{
    public bool Convert(int? code, ResolutionContext _) => code == 403;
}

但我完全被以下IsOutOfBusiness 映射所困扰!有没有机会将int? → bool 转换为字符串并将Organization.DunsControlStatus.OperatingStatus.DnbCode 指向我的“非类型化”映射。

【问题讨论】:

  • edit您的问题将您的源代码包含为minimal reproducible example,其他人可以对其进行测试并显示您希望如何使用自动映射器。
  • 能否请包括您要映射的源 (Api1.Organization) 和目标 (OrganizationDestinationModel) 模型?
  • 不客气。我推送了所有必要的代码来运行一个最小的例子。

标签: c# automapper


【解决方案1】:

就像ConvertUsing 方法不接受导航属性方案一样。也许你可以在 lambda 中使用其他 ForMember 的重载和导航属性:

.ForMember(
    d => d.IsOutOfBusiness,
    opts => opts.MapFrom(s => (s.Organization.DunsControlStatus.OperatingStatus.DnbCode ?? 0) == 403)
)

您可以在文档中找到其他示例:Custom Value Resolvers

在您的情况下,源类型未知。您可以使用反射导航到 DnbCode :

private static void MapToOrganizationDestinationModel<TSource>(this IMapperConfigurationExpression config)
{
    config
        .CreateMap<TSource, OrganizationDestinationModel>()
        ...
        .ForMember(
            destination => destination.IsOutOfBusiness,
            opts => opts.MapFrom(org => (org.DnbCode() ?? 0) == 403)
        );
}

private static int? DnbCode(this object org)
{
    var dcs = org.GetType().GetProperty(nameof(Api1.Organization.DunsControlStatus)).GetValue(org);
    var oss = dcs.GetType().GetProperty(nameof(Api1.DunsControlStatus.OperatingStatus)).GetValue(dcs);
    var dnb = oss.GetType().GetProperty(nameof(Api1.OperatingStatus.DnbCode)).GetValue(oss);
    return dnb as int?;
}

AutoMapper 可能存在更优雅的方式,但我没有找到...

【讨论】:

  • 这不是解决办法!我不能写一个IMemberValueResolver&lt;???, OrganizationDestinationModel, int?, bool&gt;,它可以像这样.MapFrom&lt;IsOutOfBusinessMemberValueResolver, int?&gt;("DunsControlStatus.OperatingStatus.DnbCode") 传递。如果我错了,请纠正我!
  • 这就是答案。 ConvertUsing 不支持。
  • @Marcel,确实我错过了源类型未知这一点。我编辑了我的答案。
【解决方案2】:

我了解您喜欢将ValueConverterConvertUsing 一起使用,但如果您可以修改目标模型(我希望可以这样做)并且不使用ValueConverter,您可以获得相同的结果:

通过添加新属性DnbCode 更新目标模型并修改IsOutOfBusiness 如下

public class OrganizationDestinationModel
{
    public string Duns { get; set; }
    public string Name { get; set; }
    public int? DnbCode { get; set; }
    public bool IsOutOfBusiness { get { return (DnbCode ?? 0) == 403;  } }
}

更新MapToOrganizationDestinationModel 方法以映射新属性DnbCode 如下

    public static void MapToOrganizationDestinationModel<T>(this IMapperConfigurationExpression config)
    {
        config
            .CreateMap<T, OrganizationDestinationModel>()
            .ForMember(
                destination => destination.Name,
                options => options.MapFrom(nameof(Api1.Organization.PrimaryName))
            )
            .ForMember(
                destination => destination.DnbCode,
                opts => opts.MapFrom("DunsControlStatus.OperatingStatus.DnbCode")
            );
    }

完成.. IsOutOfBusiness 属性不需要映射

【讨论】:

    【解决方案3】:

    我会建议这个解决方案:

    private static void MapToOrganizationDestinationModel<TSource>(this IMapperConfigurationExpression config)
    {
        //source
        var source = Expression.Parameter(typeof(TSource), "source");
        //source.DunsControlStatus
        var body = Expression.Property(source, nameof(Api1.Organization.DunsControlStatus));
        //source.DunsControlStatus.OperatingStatus
        body = Expression.Property(body, nameof(Api1.DunsControlStatus.OperatingStatus));
        //source.DunsControlStatus.OperatingStatus.DnbCode
        body = Expression.Property(body, nameof(Api1.OperatingStatus.DnbCode));
        //source => source.DunsControlStatus.OperatingStatus.DnbCode
        var dnbCodeSelector = Expression.Lambda<Func<TSource, int?>>(body, source);
    
        config
            .CreateMap<TSource, OrganizationDestinationModel>()
            .ForMember(
                destination => destination.Name,
                options => options.MapFrom(nameof(Api1.Organization.PrimaryName))
            )
            .ForMember(
                destination => destination.IsOutOfBusiness,
                options => options.MapFrom(
                    new IsOutOfBusinessMemberValueResolver<TSource>(),
                    dnbCodeSelector //source => source.DunsControlStatus.OperatingStatus.DnbCode
                    )
            );
    }
    

    我承认dnbCodeSelector 的组成确实很丑,但这是保持目标模型不变并将ValueResolver 简化为可重复使用的“转换器”的唯一方法:

    public class IsOutOfBusinessMemberValueResolver<TSource> : IMemberValueResolver<TSource, object, int?, bool>
    {
        public bool Resolve(TSource source, object destination, int? sourceMember, bool destMember, ResolutionContext context)
        {
            return sourceMember == 403;
        }
    }
    

    也许 AutoMapper 可以在未来的版本中简化此解决方案?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-03-11
      • 1970-01-01
      • 2015-12-19
      • 2020-01-25
      • 1970-01-01
      • 1970-01-01
      • 2011-08-08
      • 1970-01-01
      相关资源
      最近更新 更多