2017-01-24 3 views
2

У нас довольно распространенный сценарий, когда мы используем Automapper для отображения DTO и объектов. Как и следовало ожидать, многие свойства 1 = 1 в обоих классах с некоторыми исключениями здесь и там.Обнаружение отсутствующих или неправильно сконфигурированных DTO и объектов

Поскольку количество классов и свойств выросло, иногда разработчики забывают сохранять свойства в синхронизации при переименовании или удалении их.

Можете ли вы предложить, как мы могли бы надежно обнаруживать «незапечатанные» свойства, желательно автоматически?

+0

когда вы надеетесь, что это автоматическое обнаружение произойдет? С помощью инструмента, вызывающего? –

+0

Мы планировали написать модульные тесты, а также сделать это для каждой пары не представляется возможным. –

ответ

3

Для такой проблемы я бы рекомендовал использовать метод GetUnmappedPropertyNamesIMapper. Код и тест должен объяснить идею ниже:

Условие (z.PropertyType.IsValueType || z.PropertyType.IsArray || z.PropertyType == TypeOf (строка))

обнаружит неотображённые свойства от Типы значений, такие как int, enum, Guid, DateTime, все типы Nullable value bool ?, Decimal ?, Guid ?, и string.

И такой фильтр пусть ваш тест игнорировать отображение для Entity Навигация свойств вида:

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

Proving тест к вашим услугам:

[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

Существует также альтернативный подход с использованием метода 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(); 
    } 

} 
Смежные вопросы