我们有一个非常常见的场景,我们使用Automapper来映射DTO和实体。正如您所期望的那样,在这两个类中,很多属性都是1 = 1,但有一些例外。
随着类和属性数量的增加,有时开发人员在重命名或删除属性时忘记保持属性同步。
您能否建议我们如何可靠地检测"未映射"属性,最好是自动?
答案 0 :(得分:3)
对于这样的问题,我建议使用GetUnmappedPropertyNames
本身的IMapper
方法。代码和测试应该解释下面的想法:
条件 (z.PropertyType.IsValueType || z.PropertyType.IsArray || z.PropertyType == typeof(string))
将从值类型中检测未映射的属性,如int,enum,Guid,DateTime,所有Nullable值类型bool?,Decimal?,Guid?和string。
这样的过滤器让你的测试忽略了实体导航属性的映射:
public virtual Class NavigationProperty {get;set}
public virtual IList<Class> CollectionNavigationProperty { get; set; }
代码和测试:
[Test]
public void Mapping_Profile_Must_Not_Have_Unmapped_Properties()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<TestProfile>();
});
var mapper = config.CreateMapper();
var unmappedProperties = GetUnmappedSimpleProperties(mapper);
Assert.AreEqual(unmappedProperties.Count, 0);
}
private List<UnmappedProperty> GetUnmappedSimpleProperties(IMapper mapper)
{
return mapper.ConfigurationProvider.GetAllTypeMaps()
.SelectMany(m => m.GetUnmappedPropertyNames()
.Where(x =>
{
var z = m.DestinationType.GetProperty(x);
return z != null && (z.PropertyType.IsValueType || z.PropertyType.IsArray || z.PropertyType == typeof(string));
})
.Select(n => new UnmappedProperty
{
DestinationTypeName = m.DestinationType.Name,
PropertyName = n,
SourceTypeName = m.SourceType.Name
})).ToList();
}
internal class UnmappedProperty
{
public string PropertyName { get; set; }
public string DestinationTypeName { get; set; }
public string SourceTypeName { get; set; }
public override string ToString()
{
return $"{this.PropertyName}: {this.SourceTypeName}->{this.DestinationTypeName}";
}
}
在您的服务中进行测试:
[Test]
public void Test_Mapping_Profile_Must_Detect_Unmapped_Properties()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<TestMappingProfile>();
});
ar mapper = config.CreateMapper();
var unmappedProperties = GetUnmappedSimpleProperties();
Assert.AreEqual(unmappedProperties.Count, 12);
}
public class TestMappingProfile : Profile
{
public TestMappingProfile()
{
CreateMap<Source, DestinationValid>();
CreateMap<Source, DestinationInvalid>();
}
}
internal class Source
{
public string Test1 { get; set; }
public int Test2 { get; set; }
public int? Test3 { get; set; }
public decimal Test4 { get; set; }
public string[] Test5 { get; set; }
public Guid Test6 { get; set; }
public Guid? Test7 { get; set; }
public TransactionRealm Test8 { get; set; }
public bool? Test9 { get; set; }
public bool Test10 { get; set; }
public DateTime Test11 { get; set; }
public DateTime? Test12 { get; set; }
}
internal class DestinationValid
{
public string Test1 { get; set; }
public int Test2 { get; set; }
public int? Test3 { get; set; }
public decimal Test4 { get; set; }
public string[] Test5 { get; set; }
public Guid Test6 { get; set; }
public Guid? Test7 { get; set; }
public TransactionRealm Test8 { get; set; }
public bool? Test9 { get; set; }
public bool Test10 { get; set; }
public DateTime Test11 { get; set; }
public DateTime? Test12 { get; set; }
}
internal class DestinationInvalid
{
public string Test1X { get; set; }
public int Test2X { get; set; }
public int? Test3X { get; set; }
public decimal Test4X { get; set; }
public string[] Test5X { get; set; }
public Guid Test6X { get; set; }
public Guid? Test7X { get; set; }
public TransactionRealm Test8X { get; set; }
public bool? Test9X { get; set; }
public bool Test10X { get; set; }
public DateTime Test11X { get; set; }
public DateTime? Test12X { get; set; }
}
其中TransactionRealm是枚举的示例:
public enum TransactionRealm
{
Undefined = 0,
Transaction = 1,
Fee = 2,
}
答案 1 :(得分:1)
还有MapperConfiguration.AssertConfigurationIsValid()
方法的替代方法,可以在单元测试和运行时使用。 AssertConfigurationIsValid()
方法抛出异常,详细描述了所有检测到的未映射属性。在业务逻辑中,我建议(为了更好的性能)使用自定义MapperFactory
帮助程序在服务静态构造函数中初始化映射器:
public class MyBLL
{
private static IMapper _mapper;
static MyBLL()
{
_mapper = MapperFactory.CreateMapper<DtoToEntityDefaultProfile>();
}
}
public static class MapperFactory
{
public static IMapper CreateMapper<T>() where T : Profile, new()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<T>();
});
/// AssertConfigurationIsValid will detect
/// all unmapped properties including f.e Navigation properties, Nested DTO classes etc.
config.AssertConfigurationIsValid();
config.CompileMappings();
return config.CreateMapper();
}
}