Как мне использовать automapper для объектов с несколькими атрибутами XmlElementAttributes?

#c# #xml #xsd #automapper

#c# #xml #xsd #automapper

Вопрос:

У меня возникли проблемы с настройкой automapper для объектов, сгенерированных в файле .cs из .xsd.

Не совсем уверен, как решить проблему, когда объект имеет несколько атрибутов, как показано ниже:

Просматривал TypeConverters и т.д., Но не совсем уверен, как это правильно настроить. Уже некоторое время использую automapper и не испытываю проблем, пока к одному элементу не подключено несколько атрибутов.

 public partial class customerInfo {

    private object itemField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute("customerInfoBasic", typeof(customerInfoBasic))]
    [System.Xml.Serialization.XmlElementAttribute("customerInfoSimple", typeof(customerInfoSimple))]
    [System.Xml.Serialization.XmlElementAttribute("customerInfoEnhanced", typeof(customerInfoEnhanced))]
    public object Item {
        get {
            return this.itemField;
        }
        set {
            this.itemField = value;
        }
    }
}

public partial class customerInfoBasic{

    private string nameField;

    /// <remarks/>
    public string name {
        get {
            return this.nameField;
        }
        set {
            this.nameField= value;
        }
    }
}

public partial class customerInfoSimple{

    private string nameField;
    private string idField;


    /// <remarks/>
    public string name {
        get {
            return this.nameField;
        }
        set {
            this.nameField= value;
        }
    }

    public string id {
        get {
            return this.idField;
        }
        set {
            this.idField= value;
        }
    }
}

public partial class customerInfoEnhanced{

    private string nameField;
    private string idField;
    private string ageField;

    /// <remarks/>
    public string name {
        get {
            return this.nameField;
        }
        set {
            this.nameField= value;
        }
    }

    public string id {
        get {
            return this.idField;
        }
        set {
            this.idField= value;
        }
    }

    public string age {
        get {
            return this.ageField;
        }
        set {
            this.ageField= value;
        }
    }
}
  

Проблема, с которой я сталкиваюсь, заключается в том, что я не знаю, как настроить его, чтобы CustomerInfo правильно отображался в зависимости от некоторого значения в «Info».

Например, если «Info» содержит «age» и «id», это должно быть сопоставлено с customerInfoEnhanced и т.д.

 public static void AddSessionTransformationMappings(IMapperConfiguration cfg)
{
    cfg.AllowNullCollections = true;

    cfg.CreateMap<IEnumerable<Info>, customerInfoList>()
        .ForMember(x => x.customerInfo, x => x.MapFrom(y => y));

    cfg.CreateMap<Info, customerInfo>()
        .ForMember(x => x.Item, x => x.MapFrom(y => y));

    cfg.CreateMap<Info, customerInfoBasic>()
        .ForMember(x => x.Name, x => x.MapFrom(y => y.name));

    cfg.CreateMap<Info, customerInfoSimple>()
        .ForMember(x => x.Name, x => x.MapFrom(y => y.name))
        .ForMember(x => x.Id, x => x.MapFrom(y => y.id));

    cfg.CreateMap<Info, customerInfoEnhanced>()
        .ForMember(x => x.Name, x => x.MapFrom(y => y))
        .ForMember(x => x.Id, x => x.MapFrom(y => y.id))
        .ForMember(x => x.Age, x => x.MapFrom(y => y.age));
}
  

Вот также код для сериализатора:

 var output = provider.Transform(new List<Info> { input });

customerInfoList actual = null;
XmlSerializer serializer = new XmlSerializer(typeof(customerInfoList));
using (MemoryStream ms = new MemoryStream())
{
    serializer.Serialize(ms, output);
    ms.Position = 0;
    actual = (customerInfoList)serializer.Deserialize(ms);
}
  

Если я установлю .ForMember(x => x.customerInfo, x => x.MapFrom(y => (Object)null));
код работает, и «actual» выдает мне список с item = null, как и ожидалось, поэтому я знаю, что проблема связана с отображением «Item» в CustomerInfo.

Я ожидаю, что mapper сопоставит с правильным классом, прямо сейчас я получаю либо отсутствующую карту типов, либо «Информация не ожидалась. Используйте атрибут XmlInclude или SoapInclude, чтобы указать типы, которые не известны статически.

Был бы очень признателен за несколько советов о том, как решить проблему!

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

1. Классу CustomerInfo нужны три свойства. У вас не может быть одного свойства с тремя разными именами тегов. Единственный другой способ справиться с этим — иметь три класса, которые наследуют один и тот же базовый класс, и тогда вам нужно будет использовать свойство INCLUDE для указания трех унаследованных классов.

2. @jdweng Это то, что я подозревал, поскольку класс CustomerInfo находится в файле .cs, сгенерированном из customers .xsd, я думаю, мне придется попросить их изменить структуру xml-схемы. Спасибо за быстрый ответ!

3. Похоже, им действительно нужен базовый класс CustomerInfo, который имеет три унаследованных класса.

Ответ №1:

Решил это для своих нужд, решение ниже, если кто-то другой столкнется с такой же проблемой.

Решением для меня было использовать ResolveUsing вместо MapFrom для конкретных свойств, у которых было несколько имен тегов, и использовать Mapper.Сопоставьте с правильными классами для разных случаев.

Полный код выглядит следующим образом:

 public static void AddSessionTransformationMappings(IMapperConfiguration cfg)
{
    cfg.AllowNullCollections = true;

    cfg.CreateMap<IEnumerable<Info>, customerInfoList>()
        .ForMember(x => x.customerInfo, x => x.MapFrom(y => y));

    cfg.CreateMap<Info, customerInfo>()
        .ForMember(x => x.Item, x => x.ResolveUsing(y => CustomerInfoLevel(y)));

    cfg.CreateMap<Info, customerInfoBasic>()
        .ForMember(x => x.Name, x => x.MapFrom(y => y.name));

    cfg.CreateMap<Info, customerInfoSimple>()
        .ForMember(x => x.Name, x => x.MapFrom(y => y.name))
        .ForMember(x => x.Id, x => x.MapFrom(y => y.id));

    cfg.CreateMap<Info, customerInfoEnhanced>()
        .ForMember(x => x.Name, x => x.MapFrom(y => y.name))
        .ForMember(x => x.Id, x => x.MapFrom(y => y.id))
        .ForMember(x => x.Age, x => x.MapFrom(y => y.age));
}

private static Object CustomerInfoLevel(Info info)
{
    if (info.age != null)
    {
        return Mapper.Map<customerInfoEnhanced>(info);
    }
    else if (info.id != null)
    {
        return Mapper.Map<customerInfoSimple>(info);
    }
    else
    {
        return Mapper.Map<customerInfoBasic>(info);
    }
}
  

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

1. AM не чувствителен к регистру, поэтому большинство этих MapFrom-ов не нужны.

2. @LucianBargaoanu Да, я в курсе этого, приведенный выше код не является моим фактическим кодом и предназначен только для того, чтобы упростить понимание проблемы и решения. Большое спасибо за совет!