AutoMapper: проблема с типом сопоставления записей

#c# #automapper

#c# #automapper

Вопрос:

Я сопоставляю с automapper 10.1.1 в c # 9 из этого класса

 public record BFrom 
{
    public Guid Id { get; init; }
    public Guid DbExtraId { get; init; }
}
 

в это

 public record ATo(Guid Id, Guid ExtraId) : BaseTo(Id);
 

И имеют следующую конфигурацию

 CreateMap<BFrom, ATo>()
    .ForMember("ExtraId", opt => opt.MapFrom(src => src.DbExtraId))
    .ReverseMap();
 

Но у меня возникает проблема, когда я пытаюсь сопоставить, он выдает исключение с сообщением, в котором должен быть конструктор с 0 аргументами или только необязательными аргументами. Возможно ли устранить эту проблему без изменения типов записей?

Комментарии:

1. docs.automapper.org/en/latest/Construction.html

Ответ №1:

У меня была такая же проблема, и в итоге я создал этот метод расширения для ее решения:

 public static class AutoMapperExtensions
{
    public static IMappingExpression<TSource, TDestination> MapRecordMember<TSource, TDestination, TMember>(
        this IMappingExpression<TSource, TDestination> mappingExpression,
        Expression<Func<TDestination, TMember>> destinationMember, Expression<Func<TSource, TMember>> sourceMember)
    {
        var memberInfo = ReflectionHelper.FindProperty(destinationMember);
        string memberName = memberInfo.Name;
        return mappingExpression
            .ForMember(destinationMember, opt => opt.MapFrom(sourceMember))
            .ForCtorParam(memberName, opt => opt.MapFrom(sourceMember));
    }
}
 

Затем вы просто используете его следующим образом:

 CreateMap<BFrom, ATo>()
    .MapRecordMember(a => a.ExtraId, src => src.DbExtraId)
    .ReverseMap();
 

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

Комментарии:

1. Попробуйте сборку MyGet . Это больше не требуется.

2. Спасибо @LucianBargaoanu, это полезно знать 👍

3. @LucianBargaoanu, похоже, все еще требуется (проверено 10.1.2-alpha.0.4, 11.0.0 и 11.0.1-alpha.0.6)

4. github.com/AutoMapper/AutoMapper/issues/3834

Ответ №2:

Что вам нужно, так это указать конструктор-параметр по имени в вашем профиле сопоставления, например public AToProfile() => CreateMap<BFrom, ATo>().ForCtorParam(ctorParamName: "ExtraId", m => m.MapFrom(s => s.DbExtraId)).ReverseMap(); , таким образом, AutoMapper будет знать, откуда должно быть взято значение, и будет соответствующим образом отображаться.

 public class StackoverflowQuestionTest
{
    [Fact]
    public void BFrom_MapTo_ATo()
    {
        // Arrange
        IConfigurationProvider configuration = new MapperConfiguration(cfg => cfg.AddProfile<AToProfile>());
        var source = new BFrom {Id = Guid.NewGuid()};

        // Act
        var target = new Mapper(configuration).Map<ATo>(source);

        // Assert
        target.Id.Should().Be(source.Id);
    }


}

public record BaseTo (Guid Id); // this is an assumption, as you did not specify BaseTo in you question

public record BFrom
{
    public Guid Id { get; init; }
    public Guid DbExtraId { get; init; }
}

public record ATo(Guid Id, Guid ExtraId) : BaseTo(Id);

public class AToProfile : Profile
{
    public AToProfile() =>
        CreateMap<BFrom, ATo>()
            .ForCtorParam(ctorParamName: "ExtraId", m => m.MapFrom(s => s.DbExtraId))
            .ReverseMap();
}
 

Комментарии:

1. Даже не знал, что существует параметр ForCtorParam, спасибо, приятель.

Ответ №3:

Попробуйте

 CreateMap<BFrom, ATo>().DisableCtorValidation()
    .ForMember("ExtraId", opt => opt.MapFrom(src => src.DbExtraId))
    .ReverseMap()
 

Комментарии:

1. Но это не сработает, если вы хотите, чтобы AutoMapper создавал новый ATo ? Т.е. mapper.Map<ATo>(new BFrom(...)) Сбой с needs to have a constructor with 0 args or only optional args...

Ответ №4:

Используйте более статичный подход.. мы знаем имя конструктора записей..

 public record ATo(Guid Id, Guid ExtraId) : BaseTo(Id);

CreateMap<BFrom, ATo>()
    .ForMember(nameof(ATo.Extra), opt => opt.MapFrom(src => src.DbExtraId))
    .ReverseMap();
 

Объяснение / Глубокое погружение

Записи на самом деле являются просто синтаксическим сахаром для концепции, которая существует всегда, то есть они предлагают неизменяемые свойства. Записи на самом деле делают AutoMapper со старой концепцией проще и безопаснее, потому что с новым синтаксисом мы знаем, какими будут имена свойств, т.е. record Example(string SomeProperty) Компилятор просто создаст конструктор следующим образом,

 public class Example
{
    public string SomeProperty { get; init; }         
    Example(string SomeProperty) 
    {
        this.SomeProperty = SomeProperty;
    }
}
 

Создание пустого конструктора вам не поможет, потому что свойства являются init единственными, и вы не можете использовать инициализатор объекта, потому что вам нужно использовать конструктор … и хорошо, что инициализаторы объектов являются особым случаем для init свойств, то есть они позволяют изменять свойства, даже если инициализаторы объектов сами по себе являются просто синтаксическим сахаром. На самом деле нет способа с помощью отражения использовать инициализаторы объектов, так что лазейка исчезла для библиотек, таких как AutoMapper, лазейкой является..
new Example(default){SomeProperty = "SomeValue"} .

Теперь, конечно, вы можете использовать отражение для изменения свойств только для чтения / частных / внутренних, которые я не уверен, поддерживает ли AutoMapper или нет, я надеюсь, что это не так, потому что это не очень хорошая вещь по причинам, хорошо документированным в Интернете.