Serializar un int que acepta valores NULL

92

¿Tengo una clase con un int que acepta valores NULL? tipo de datos configurado para serializar como un elemento xml. ¿Hay alguna forma de configurarlo para que el serializador xml no serialice el elemento si el valor es nulo?

Intenté agregar el atributo [System.Xml.Serialization.XmlElement (IsNullable = false)], pero obtengo una excepción de serialización en tiempo de ejecución que dice que hubo un error que refleja el tipo, porque "IsNullable puede no estar configurado en 'falso 'para un tipo que acepta valores NULL. Considere usar el tipo' System.Int32 'o eliminar la propiedad IsNullable del atributo XmlElement ".

[Serializable]
[System.Xml.Serialization.XmlRoot("Score", Namespace = "http://mycomp.com/test/score/v1")]
public class Score
{
    private int? iID_m;
    ...

    /// <summary>
    /// 
    /// </summary>        
    public int? ID 
    { 
        get 
        { 
            return iID_m; 
        } 
        set 
        { 
            iID_m = value; 
        } 
    }
     ...
}

La clase anterior se serializará para:

<Score xmlns="http://mycomp.com/test/score/v1">
    <ID xsi:nil="true" />
</Score>

Pero para los ID que son nulos, no quiero el elemento ID en absoluto, principalmente porque cuando uso OPENXML en MSSQL, devuelve un 0 en lugar de nulo para un elemento que se parece a

Jeremy
fuente

Respuestas:

149

XmlSerializer admite el ShouldSerialize{Foo}()patrón, por lo que puede agregar un método:

public bool ShouldSerializeID() {return ID.HasValue;}

También existe el {Foo}Specifiedpatrón: no estoy seguro de si XmlSerializer lo admite.

Marc Gravell
fuente
8
XmlSerializer también admite el patrón [Foo} Specified.
David Schmitt
23
La página relevante está aquí: msdn.microsoft.com/en-us/library/53b8022e%28VS.71%29.aspx
cbp
1
¿Alguna forma de usar ShouldSerialize <prop> con propiedades generadas automáticamente? es decir, sin variable local.
Jay
1
@Jay: No es necesario. Puede llamar HasValuea la propiedad.
Steven Sudit
1
@mark si, para miembro (propiedad / campo) Footambién tiene un public bool FooSpecified {get {...} set {...}}, entonces getse usa para ver si se Foodebe serializar, y setse llama al asignar un valor Foodurante la deserialización.
Marc Gravell
26

Estoy usando este micropatrón para implementar la serialización anulable:

[XmlIgnore]
public double? SomeValue { get; set; }

[XmlAttribute("SomeValue")] // or [XmlElement("SomeValue")]
[EditorBrowsable(EditorBrowsableState.Never)]
public double XmlSomeValue { get { return SomeValue.Value; } set { SomeValue= value; } }  
[EditorBrowsable(EditorBrowsableState.Never)]
public bool XmlSomeValueSpecified { get { return SomeValue.HasValue; } }

Esto proporciona la interfaz correcta para el usuario sin compromiso y aún hace lo correcto al serializar.

David Schmitt
fuente
1
Dado que SomeValue puede ser nulo ... public double XmlSomeValue {get {return SomeValue.HasValue? SomeValue.Value: 0; } set {SomeValue = value; }}
Doug Domeny
Se supone que XmlSomeValue solo debe ser utilizado por XmlSerializer, que solo lo tocará cuando XmlSomeValueSpecified sea verdadero (es decir, SomeValue.Value no es nulo.
David Schmitt
@pettys: Es XML, ¿qué esperas? ;-)
David Schmitt
La respuesta aceptada es de 2008. Esta debería ser la de ahora. Respuesta interesante relacionada con Specified vs ShouldSerialize
daniloquio
Definitivamente debería ser la respuesta principal.
tyteen4a03
12

Descubrí una solución alternativa utilizando dos propiedades. ¿Un int? propiedad con un atributo XmlIgnore y una propiedad de objeto que se serializa.

    /// <summary>
    /// Score db record
    /// </summary>        
    [System.Xml.Serialization.XmlIgnore()]
    public int? ID 
    { 
        get 
        { 
            return iID_m; 
        } 
        set 
        { 
            iID_m = value; 
        } 
    }

    /// <summary>
    /// Score db record
    /// </summary>        
    [System.Xml.Serialization.XmlElement("ID",IsNullable = false)]
    public object IDValue
    {
        get
        {
            return ID;
        }
        set
        {
            if (value == null)
            {
                ID = null;
            }
            else if (value is int || value is int?)
            {
                ID = (int)value;
            }
            else
            {
                ID = int.Parse(value.ToString());
            }
        }
    }
Jeremy
fuente
Esta solución es excelente ya que también permite codificar NULL como valor de "marcador de posición" para los clientes que no reconocen NULL en ints, es decir, Flex.
Kuba Wyrostek
Puede agregar [EditorBrowsable (EditorBrowsableState.Never)] a la propiedad serializada xml para que aviod la vea al codificar
Antonio Rodríguez
6

Wow, gracias, esta pregunta / respuesta realmente me ayudó. Yo corazón Stackoverflow.

Hice lo que estás haciendo arriba un poco más genérico. Todo lo que realmente estamos buscando es tener Nullable con un comportamiento de serialización ligeramente diferente. Usé Reflector para construir mi propio Nullable, y agregué algunas cosas aquí y allá para que la serialización XML funcione de la manera que queremos. Parece funcionar bastante bien:

public class Nullable<T>
{
    public Nullable(T value)
    {
        _value = value;
        _hasValue = true;
    }

    public Nullable()
    {
        _hasValue = false;
    }

    [XmlText]
    public T Value
    {
        get
        {
            if (!HasValue)
                throw new InvalidOperationException();
            return _value;
        }
        set
        {
            _value = value;
            _hasValue = true;
        }
    }

    [XmlIgnore]
    public bool HasValue
        { get { return _hasValue; } }

    public T GetValueOrDefault()
        { return _value; }
    public T GetValueOrDefault(T i_defaultValue)
        { return HasValue ? _value : i_defaultValue; }

    public static explicit operator T(Nullable<T> i_value)
        { return i_value.Value; }
    public static implicit operator Nullable<T>(T i_value)
        { return new Nullable<T>(i_value); }

    public override bool Equals(object i_other)
    {
        if (!HasValue)
            return (i_other == null);
        if (i_other == null)
            return false;
        return _value.Equals(i_other);
    }

    public override int GetHashCode()
    {
        if (!HasValue)
            return 0;
        return _value.GetHashCode();
    }

    public override string ToString()
    {
        if (!HasValue)
            return "";
        return _value.ToString();
    }

    bool _hasValue;
    T    _value;
}

¿Pierde la capacidad de tener a sus miembros como int? y así sucesivamente (tiene que usar Nullable <int> en su lugar) pero aparte de eso, todo el comportamiento permanece igual.

Scobi
fuente
1
Esto arroja un System.ExecutionEngineExceptionsobre XmlSerializer.Serializede mí.
Martin Braun
1

Desafortunadamente, los comportamientos que describe están documentados con precisión como tales en los documentos de XmlElementAttribute.IsNullable.

Serge Wautier
fuente
1

Una publicación muy útil ayudó mucho.

Opté por ir con la revisión de Scott al tipo de datos Nullable (Of T), sin embargo, el código publicado todavía serializa el elemento Nullable cuando es Null, aunque sin el atributo "xs: nil = 'true'".

Necesitaba forzar al serializador a eliminar la etiqueta por completo, así que simplemente implementé IXmlSerializable en la estructura (esto está en VB, pero te dan la imagen):

  '----------------------------------------------------------------------------
  ' GetSchema
  '----------------------------------------------------------------------------
  Public Function GetSchema() As System.Xml.Schema.XmlSchema Implements System.Xml.Serialization.IXmlSerializable.GetSchema
    Return Nothing
  End Function

  '----------------------------------------------------------------------------
  ' ReadXml
  '----------------------------------------------------------------------------
  Public Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements System.Xml.Serialization.IXmlSerializable.ReadXml
    If (Not reader.IsEmptyElement) Then
      If (reader.Read AndAlso reader.NodeType = System.Xml.XmlNodeType.Text) Then
         Me._value = reader.ReadContentAs(GetType(T), Nothing)
      End If
    End If
  End Sub

  '----------------------------------------------------------------------------
  ' WriteXml
  '----------------------------------------------------------------------------
  Public Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements System.Xml.Serialization.IXmlSerializable.WriteXml
    If (_hasValue) Then
      writer.WriteValue(Me.Value)
    End If
  End Sub

Prefiero este método a usar el patrón (foo) Specified, ya que requiere agregar un montón de propiedades redundantes a mis objetos, mientras que usar el nuevo tipo Nullable solo requiere volver a escribir las propiedades.

James Close
fuente