2016-04-29 3 views
2

Мы используем атрибут nilReason, чтобы выразить, почему XML-элемент пуст. Примеры:XML-сериализация элементов без элементов с атрибутами в C#

<dateOfDeath nilReason="noValue" xsi:nil="true"/> 
<dateOfDeath nilReason="valueUnknown" xsi:nil="true"/> 

В первом примере человек все еще жив, потому что нет даты смерти. Во втором примере мы не знаем, какова дата смерти.

XSD-определение этого элемента приводится ниже:

<xs:element name="dateOfDeath" type="DateOfDeath" nillable="true"/> 
<xs:complexType name="DateOfDeath"> 
    <xs:simpleContent> 
     <xs:extension base="xs:date"> 
      <xs:attribute name="nilReason" type="NilReason"/> 
     </xs:extension> 
    </xs:simpleContent> 
</xs:complexType> 
<xs:simpleType name="NilReason"> 
    <xs:restriction base="xs:string"> 
     <xs:enumeration value="noValue"/> 
     <xs:enumeration value="valueUnknown"/> 
    </xs:restriction> 
</xs:simpleType> 

я столкнуться с проблемами, когда я генерировать C# классы с помощью инструмента Xsd.exe, который предоставляется в рамках .net. Как написать код, который создает следующий XML?

<dateOfDeath nilReason="noValue" xsi:nil="true"/> 

Это наилучшее приближение код, который я смог написать:

DateOfDeath dateOfDeath = new DateOfDeath(); 
dateOfDeath.nilReason = NilReason.noValue; 
dateOfDeath.nilReasonSpecified = true; 
XmlSerializer serializer = new XmlSerializer(typeof(DateOfDeath)); 
StreamWriter writer = new StreamWriter("dateofdeath.xml"); 
serializer.Serialize(writer, dateOfDeath); 
writer.Close(); 

Однако, к сожалению, этот код выдает следующий результат:

<dateOfDeath nilReason="noValue">0001-01-01</dateOfDeath> 

, который не является именно то, что Я хочу, потому что он генерирует значение фиктивной даты. Похоже, что это недостаток сериализатора. Единственный способ обойти эту проблему, похоже, заключается в применении функции, которая удаляет фиктивное значение и вставляет атрибут xsi: nil = "true" после сериализации. Тогда также нужна функция, которая удаляет атрибут xsi: nil = "true" перед десериализацией. В противном случае информация атрибута nilReason будет выбрасываться во время процесса десериализации.

+1

Конечно, 'DateofDeath = новый DateOfDeath() '- i t не равно нулю. –

+0

@AlexanderPetrov: на самом деле объект dateOfDeath не является нулевым, потому что мне нужно установить такие свойства, как 'dateOfDeath.noValue =" noValue "', который соответствует атрибуту XML noValue. Однако я не могу установить свойство 'dateOfDeath.Value = null', которое соответствует пустому содержимому самого элемента. Эта проблема вызвана тем, что сгенерированный тип данных DateTime не является nillable. Я попытался решить эту проблему, заменив все вхождения DateTime на DateTime? в сгенерированном коде. Но затем сериализатор выдает ошибку. –

+0

Вы получите 'xsi: nil =" true "' в одном случае: 'DateOfDeath dateOfDeath = null;' –

ответ

0

Следующие две функции решают проблему. Первый (addNilAttributes) добавляет атрибут xsi: nil = "true" к элементам, содержащим атрибут nilReason, и делает этот элемент пустым. Эта функция должна применяться после сериализации.

static public XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance"; 

    static public XElement addNilAttributes(XElement root) 
    {  
     IEnumerable<XElement> noValueElements = 
      from el in root.Descendants() 
      where (string)el.Attribute("nilReason") != null 
      select el; 

     foreach (XElement el in noValueElements) 
     { 
      el.Add(new XAttribute(xsi + "nil", "true")); 
      el.ReplaceNodes(null); // make element empty 
     } 

     IEnumerable<XElement> nilElements = 
      from el in root.Descendants() 
      where (string)el.Attribute("nilReason") == null && (string)el.Attribute(xsi + "nil") != null 
      select el; 

     nilElements.Remove(); 
     return root; 
    } 

Например, <dateOfDeath nilReason="noValue">0001-01-01</dateOfDeath> будут переведены на <dateOfDeath nilReason="noValue" xsi:nil="true"/>. Но <dateOfDeath xsi:nil="true"/> будет удален, потому что вам всегда нужно указать nilReason, если элемент пуст.

Вторая функция (removeNilAttributes) удаляет атрибуты xsi: nil перед десериализацией. В противном случае значение атрибута nilReason будет потеряно во время процесса десериализации.

static public XElement removeNilAttributes(XElement root) 
    { 
     root.DescendantsAndSelf().Attributes(xsi + "nil").Remove(); 
     return root; 
    } 

Например, <dateOfDeath nilReason="noValue" xsi:nil="true"/> будут преобразованы в <dateOfDeath nilReason="noValue"/> прежде, чем десериализации.

Ниже приведен пример кода, как можно применить эти две функции:

 DateOfDeath dateOfDeath = new DateOfDeath(); 
     dateOfDeath.nilReason = NilReasonType.noValue; 
     dateOfDeath.nilReasonSpecified = true; 

     XmlSerializer serializer = new XmlSerializer(typeof(DateOfDeath)); 

     StringWriter writer = new StringWriter(); 
     serializer.Serialize(writer, dateOfDeath); 
     String str = writer.ToString(); 
     Console.WriteLine(str);   
     writer.Close(); 

     XElement root = XElement.Parse(str); 

     root = addNilAttributes(root); 
     Console.WriteLine(root.ToString()); 

     root = removeNilAttributes(root); 
     Console.WriteLine(root.ToString()); 

     StringReader reader = new StringReader(root.ToString());   
     DateOfDeath dateOfDeath2 = new DateOfDeath(); 
     dateOfDeath2 = (DateOfDeath)serializer.Deserialize(reader); 

Выход:

<dateOfDeath xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd=" tp://www.w3.org/2001/XMLSchema" nilReason="noValue">0001-01-01</dateOfDeath>

<dateOfDeath xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd=" tp://www.w3.org/2001/XMLSchema" nilReason="noValue" xsi:nil="true"/>

<dateOfDeath xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd=" tp://www.w3.org/2001/XMLSchema" nilReason="noValue"/>

2

Проблема заключается в том, что атрибут генерируется бок о бок с его значение, в том же классе DateofDeath (я ушел из какой-то код для краткости):

public partial class DateOfDeath 
{ 
    private NilReason nilReasonField; 
    private bool nilReasonFieldSpecified; 
    private System.DateTime valueField; 

    [System.Xml.Serialization.XmlAttributeAttribute()] 
    public NilReason nilReason 
    { 
     get/set... 
    } 

    [System.Xml.Serialization.XmlIgnoreAttribute()] 
    public bool nilReasonSpecified 
    { 
     get/set... 
    } 

    [System.Xml.Serialization.XmlText(DataType = "date")] 
    public System.DateTime Value 
    { 
     get/set... 
    } 
} 

[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.6.81.0")] 
[System.SerializableAttribute()] 
public enum NilReason 
{ 
    noValue, 
    valueUnknown, 
} 

Так что для того, чтобы сериализовать всухую элемент, должны установить родителю нуль:

DateOfDeath dod = null; 
serializer.Serialize(stream, dod); 

производить что-то вроде:

<dateOfDeath xmlns:xsi="..." xmlns:xsd="..." xsi:nil="true" /> 

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

DateOfDeath dod = null; 
dod.nilReason = noValue; // does not work with nullpointer 

Значение однако отображается в виде текста XML-элемента, как:

<dateOfDeath xmlns:xsi="..." xmlns:xsd="...">[value]</dateOfDeath> 

Где [значение], конечно, текстовое представление вашего Дата. Таким образом, даже если вы можете установить значение в null, что вы не можете, потому что вы не можете отобразить сложный тип (например, Nullable <DateTime>) в качестве XmlText - вы все равно не сможете установить родительский элемент (<DateOfDeath>) в ноль так или иначе.

Так, может быть, ближе всего к тому, что вы хотите, чтобы значение Nullable и сделать его как XmlElement (обратите внимание на добавленную вопросительный):

private System.DateTime? valueField; 

[System.Xml.Serialization.XmlElement(DataType = "date", IsNullable = true)] 
public System.DateTime? Value { get/set ...} 

набор, обнулить

DateOfDeath dod = new DateOfDeath(); 
dod.nilReason = NilReason.noValue; 
dod.nilReasonSpecified = true; 
dod.Value = null; 

XmlSerializer serializer = new XmlSerializer(typeof(DateOfDeath)); 
serializer.Serialize(stream, dod); 

давая вам :

<?xml version="1.0" encoding="utf-8"?> 
<dateOfDeath xmlns:xsi="..." xmlns:xsd="..." nilReason="noValue"> 
    <Value xsi:nil="true" /> 
</dateOfDeath> 

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

+0

Это действительно не то, что я ищу, потому что в вашем решении XML не может быть подтвержден исходной схемой XSD, из которой был сгенерирован код.Более того, новый синтаксис выражения элемента даты становится довольно экзотическим. Но все же большое спасибо за ваш анализ, потому что это, безусловно, делает проблему более ясной, чем я –

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