2017-12-04 21 views
1

현재 목록을 지정하는 아주 이상한 방법이있는 웹 서비스로 데이터를 보내는 것이 맡고 있습니다. 나는 스키마를 제어하지 못하고 상대방이 스키마를 변경하려는 시도가 실패했다. 그래서 나는 이것에 상당히 매달렸다. 특정 순서로 C#에서 병렬 배열을 XML로 직렬화

자신의 스키마를 정의하는 방법

이가 (단지 관련 비트 포함)입니다 :
<xs:element name="Part"> 
    <xs:complexType> 
    <xs:sequence> 
     <xs:element name="List"> 
     <xs:complexType> 
      <xs:sequence maxOccurs="4"> 
      <xs:element name="Name" type="xs:string" /> 
      <xs:element name="Data" type="xs:string" /> 
      <xs:element name="OtherData" type="xs:string" /> 
      </xs:sequence> 
     </xs:complexType> 
     </xs:element> 
    </xs:sequence> 
    </xs:complexType> 
</xs:element> 

내가 쉽게 구조를 직렬화하는 C# 클래스를 생성하는 xsd.exe를 사용했다. 생성 된 비트는 다음과 같습니다.

[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.33440")] 
[System.SerializableAttribute()] 
[System.Diagnostics.DebuggerStepThroughAttribute()] 
[System.ComponentModel.DesignerCategoryAttribute("code")] 
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="the namespace")] 
public partial class PartList { 
    [System.Xml.Serialization.XmlElementAttribute("Name", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)] 
    public string[] Name { get; set; } 

    [System.Xml.Serialization.XmlElementAttribute("Data", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)] 
    public string[] Data { get; set; } 

    [System.Xml.Serialization.XmlElementAttribute("OtherData", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)] 
    public string[] OtherData { get; set; } 
} 

예, 병렬 배열입니다. 이제는 다른 문서를 통해 XML을 생성하는 다른 문서의 일화 데이터에 따라 올바른/예상 XML이 다음과 같이 표시됩니다 (예 : 두 개의 항목이있는 목록 - 설명을 위해 인라인 주석이 추가됨) :

<Part xmlns=""> 
    <List> 
     <Name>Marcos Test</Name> <!-- first item --> 
     <Name>Pepe Lama</Name> <!-- second item --> 
     <Data>123131313</Data> <!-- first item --> 
     <Data>331331313</Data> <!-- second item --> 
     <OtherData>0.11</OtherData> <!-- first item --> 
     <OtherData>0.02</OtherData> <!-- second item --> 
    </List> 
</Part> 

내 XML 때문에 항목의 순서의 스키마에 대해 유효성 검사를 실패

<Part xmlns=""> 
    <List> 
     <Name>Some Test</Name> <!-- first item --> 
     <Data>123131313</Data> <!-- first item -->   
     <OtherData>0.11</OtherData> <!-- first item -->   
     <Name>Other Lama</Name> <!-- second item --> 
     <Data>331331313</Data> <!-- second item --> 
     <OtherData>0.02</OtherData> <!-- second item --> 
    </List> 
</Part> 

는하지만, 내 자동 생성 된 C# 클래스는에 직렬화한다. System.Xml.Serialization.XmlSerializer를 기본 옵션과 함께 사용하여 클래스를 serialize합니다. 필자는 보통 다른 웹 서비스에 적합한 스키마를 직렬화하는 데 문제가 없습니다. 그러나 웬일인지 나는 나 자신의 삶에 대해 이것을 할 수있는 방법을 찾아 낼 수 없다 (심지어 가능하다면).

아이디어가 있으십니까? 이미 XmlOrderAttribute를 사용하여 시도했지만 결과의 순서에 차이를 만들지 않았습니다. 여기

+1

귀하의 문제는 (https://stackoverflow.com/a/30443169/3744182) 또는 [XML [XML로 KeyValuePair의 목록을 직렬화]의 문제와 매우 유사하다 RestSharp를 사용한 시퀀스 역 직렬화] (https://stackoverflow.com/a/32885108/3744182). 둘 다 'XmlSerializer'를 사용하여 쌍 요소의 시퀀스를 deserialize하는 두 가지 다른 방법을 보여주는 대답을 갖고 있습니다. 그 대답이 충분한가요, 아니면 더 많은 도움이 필요합니까? – dbc

+0

하나의 목록이라면 나는 그것을 하트 비트로 할 것입니다. 그러나 스키마 전체에 많은 요소가 있습니다 (5000 행 스키마 파일). 나는 이것을 구현하는 더 쉬운 방법이 있기를 바라고있다. 어쨌든 이것이 이것이 유일한 방법 일 경우, 귀하가 답변으로 게시하고 귀하를 표시하도록 제안합니다. 감사합니다. – enriquein

+0

@dbc 답을 게시하여 동의 할 수 있도록 게시하십시오. 나는이 접근법을 직렬화에 사용할 것이다. 이 코드를 컴퓨터 생성 코드에 적용하는 문제는 XmlSerializer의 부분 클래스와 XmlOverrides를 사용하여 해결되었습니다. 해결책을 이끌어 주셔서 대단히 감사합니다. – enriquein

답변

1

기본적인 문제는 XmlSerializer 재귀 XML 요소 (들)의 블록 객체 그래프와 맵 오브젝트를 내려 것입니다,하지만 당신은 인터리브 특정 개체, 즉 public string[] 속성에 의해 생성 된 요소합니다. 불행히도이 옵션은 XML serializer attributes을 사용하여 즉시 사용할 수 없습니다.

그러나 필요한 형식으로 직렬화 할 수있는 서로 게이트 속성을 도입하여 필요한 XML을 생성 할 수 있습니다. 다음과 같은 두 가지 질문에서와 같이이 작업을 수행하는 두 가지 방법이 있습니다 :

  1. Serializing a list of KeyValuePair to XML

    여러 컬렉션의 요소의 인터리브 목록을 생성 XmlSerializerpolymorphic list functionality을 사용하는 방법을 보여줍니다.

  2. Xml Sequence deserialization with RestSharp은 요소의 인터리브 된 목록을 생성하기 위해 [XmlAnyElement] 속성을 사용하는 방법을 보여줍니다.

    public partial class PartList 
    { 
        [XmlIgnore] 
        public List<string> Name { get; } = new List<string>(); 
    
        [XmlIgnore] 
        public List<string> Data { get; } = new List<string>(); 
    
        [XmlIgnore] 
        public List<string> OtherData { get; } = new List<string>(); 
    
        [System.Xml.Serialization.XmlElementAttribute("Name", typeof(Name), Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] 
        [System.Xml.Serialization.XmlElementAttribute("Data", typeof(Data), Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] 
        [System.Xml.Serialization.XmlElementAttribute("OtherData", typeof(OtherData), Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] 
        public ValueWrapper<string>[] Values 
        { 
         get 
         { 
          var list = new List<ValueWrapper<string>>(); 
          for (int i = 0, count = Math.Max(Name.Count, Math.Max(Data.Count, OtherData.Count)); i < count; i++) 
          { 
           if (i < Name.Count) 
            list.Add(new Name { Value = Name[i] }); 
           if (i < Data.Count) 
            list.Add(new Data { Value = Data[i] }); 
           if (i < OtherData.Count) 
            list.Add(new OtherData { Value = OtherData[i] }); 
          } 
          return list.ToArray(); 
         } 
         set 
         { 
          if (value == null) 
           return; 
          Name.AddRange(value.OfType<Name>().Select(v => v.Value)); 
          Data.AddRange(value.OfType<Data>().Select(v => v.Value)); 
          OtherData.AddRange(value.OfType<OtherData>().Select(v => v.Value)); 
         } 
        } 
    } 
    
    public class Name : ValueWrapper<string> { } 
    
    public class Data : ValueWrapper<string> { } 
    
    public class OtherData : ValueWrapper<string> { } 
    
    public abstract class ValueWrapper<T> : ValueWrapper where T : IConvertible 
    { 
        public override object GetValue() => Value; 
    
        [XmlText] 
        public T Value { get; set; } 
    } 
    
    public abstract class ValueWrapper 
    { 
        public abstract object GetValue(); 
    } 
    

    참고 :

    • 원래 컬렉션 [XmlIgnore] 표시되어

는 예를 들어, 여기에 접근 번호 (1)의 구현이다.

  • 유효한 유형 이름마다 하나씩 여러 유형을 포함 할 수있는 대리 다형성 배열 특성 ValueWrapper<string>[] Values이 도입되었습니다.

  • 배열을 만들고 반환 할 때 세 가지 컬렉션의 값이 인터리브됩니다. 배열 설정자에서 값은 유형별로 나누어 져 관련 컬렉션으로 지정됩니다.

  • 샘플 fiddle.

    1

    필자의 의견과 대답이 나를 최종 구현으로 이끌었 기 때문에 dbc의 대답이 정확하다고 표시했습니다. 완성을 위해 (그리고 내가 미래에 이것을 실행할 경우를 대비해 어떤 종류의 문서화를 위해서), 이것이 내가이 기능을 구현하는 방법이다.

    xsd.exe가 모든 클래스를 부분 클래스로 생성한다는 점을 이용했습니다. 따라서 일련 번호가 매겨진 새로운 목록/컬렉션 속성을 쉽게 추가 할 수 있었으며 스키마가 변경 될 때마다 변경 내용을 잃지 않았습니다. 예를 들어, RemoteServiceTypePart1 클래스의 List 속성을 무시합니다 : 이제

    public partial class RemoteServiceTypePart1 
    { 
        // Tell the Xml serializer to use "List" instead of "List_Override" 
        // as the element name. 
        [XmlAnyElement("List")] 
        public XElement List_Override 
        { 
         get { 
          var result = new List<XElement>(); 
    
          for (int i = 0; i < List.Name.Length; i++) 
          { 
           result.Add(new XElement("Name", List.Name[i])); 
           result.Add(new XElement("Data", List.Data[i])); 
           result.Add(new XElement("OtherData", List.OtherData[i])); 
          } 
    
          return new XElement("List", result.ToArray()); 
         } 
    
         set { } 
        } 
    } 
    

    을 유일하게 남아있는 문제마다 손으로 스키마 속성을 추가해야 원래의 속성에 [XmlIgnore] 속성을 추가하지 않는 방법입니다 변경. 이를 위해, 나는 XmlAttributeOverrides 클래스를 사용하여 XML 직렬화 논리가 어디에 배치 : 그것 뿐이다

    var xmlIgnoreAttr = new XmlAttributes { XmlIgnore = true }; 
    
    var overrides = new XmlAttributeOverrides(); 
    // Add an override for each class that requires properties to be ignored. 
    overrides.Add(typeof(RemoteServiceTypePart1), "List", xmlIgnoreAttr); 
    overrides.Add(typeof(RemoteServiceTypePart2), "List", xmlIgnoreAttr); 
    overrides.Add(typeof(RemoteServiceTypePart3), "List", xmlIgnoreAttr); 
    
    // In a real-world implementation, you will need to cache this object to 
    // avoid a memory leak. Read: https://stackoverflow.com/a/23897411/3744182 
    // Thanks to dbc for letting me know in the comments. 
    var ser = new XmlSerializer(typeof(RemoteServiceType), overrides); 
    // serialize, send xml, do whatever afterwards 
    

    . 이제 출력 XML은 다음과 같습니다

    <RemoteServiceTypePart1 xmlns=""> 
        <List> 
        <Name>Marcos Test</Name> 
        <Data>123131313</Data> 
        <OtherData>0.11</OtherData> 
        <Name>Pepe Lama</Name> 
        <Data>331331313</Data> 
        <OtherData>0.02</OtherData> 
        </List> 
    </RemoteServiceTypePart1> 
    
    +1

    참고 - [이 답변] (https://stackoverflow.com/a/23897411/3744182)에서 설명했듯이 메모리 누수를 방지하려면 기본이 아닌 생성자로 생성 된 'XmlSerializer'를 정적으로 캐싱하고 다시 사용해야합니다. [해당 문서] (https://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx#Remarks)를 참조하십시오. * 다른 생성자를 사용하는 경우 여러 버전의 동일한 어셈블리가 생성되어 언로드되지 않으므로 메모리 누수와 성능 저하가 발생합니다. ... 그렇지 않으면 Hashtable에 어셈블리를 캐시해야합니다. * – dbc

    +0

    큰 포착입니다. 다시 한 번 감사드립니다! – enriquein