Problem deserializing with NetDataContractSerializ

2019-04-09 02:12发布

问题:

I have a situation where I'm serializing some .NET objects using NetDataContractSerializer and storing the XML in a database as a way to remember the state of these objects within an application. Recently I just came across the first situation where some code refactoring of property and type names has resulted in the failure to deserialize this XML data.

So far I've come up with two different plans of attack for how to deal with version compatibility breaks such as these which are to use facilities available within the NetDataContractSerializer itself to control the deserialization or just to transform the XML directly. From my experimentation and research it appears that one can deserialize to a different type using a custom SerializationBinder and property name/type changes can be addressed by either implementing ISerializable or writing a serialization surrogate by implementing ISurrogateSelector and ISerializationSurrogate. Unfortunately this preferred mechanism hasn't panned out and unless I can be shown otherwise it appears using surrogates move between version of the serialized data is not possible with the NetDataContractSerializer and this is due to some unexplained design decision by Microsoft. What Microsoft has suggested is to use the same serialization on both sides which completely defeats the purpose of using a surrogate to aid in cases where a type name changes or it is moved into a different namespace or assembly.

To fix it, please use the same NetDataContractSerializer instance or another instance that is also initialized with a compatible SurrogateSelector.

This explanation conflicts with an MSDN article which has this to say about using a custom binder to substitute types along with dealing with other changes in the serialization structure.

During deserialization, the formatter sees that a binder has been set. As each object is about to be deserialized, the formatter calls the binder's BindToType method, passing it the assembly name and type that the formatter wants to deserialize. At this point, BindToType decides what type should actually be constructed and returns this type.

Note that the original type and the new type must have the same exact field names and types if the new type uses simple serialization via the Serializable custom attribute. However, the new version of the type could implement the ISerializable interface and then its special constructor will get called and the type can examine the values in the SerializationInfo object and determine how to deserialize itself.

So either I'm going to be able to get NetDataContractSerializer working to deserialize my V1 XML into my V2 types or I'm going to have to manually transform the XML. If somebody could prove that NetDataContractSerializer's SerializationInfo does in fact work when using ISerializable or using serialization surrogates that would be excellent or at least give a better explanation than the one given by Microsoft, otherwise I'll probably post a new question to debate the best way in .NET to transform the old XML directly.

UPDATE 2011-08-16: After some experimentation it appears that the ISerializable and serialization surrogate technique both work fine if the original type that was serialized implemented ISerializable otherwise if the type just used the [Serializable] attribute it appears that each field in the object graph is missing some valuable type information in the form of extra attributes.

Example using [Serializable] attribute

<OldClass2 xmlns:i="http://www.w3.org/2001/XMLSchema-instance" z:Id="1" z:Type="NdcsSurrogateTest.OldClass2" z:Assembly="NdcsSurrogateTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" xmlns="http://schemas.datacontract.org/2004/07/NdcsSurrogateTest">
  <_stable z:Id="2">Remains the same</_stable>
  <_x003C_OldProperty_x003E_k__BackingField>23</_x003C_OldProperty_x003E_k__BackingField>
</OldClass2>

Example implementing ISerialzable:

<OldClass2 xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:x="http://www.w3.org/2001/XMLSchema" z:Id="1" z:Type="NdcsSurrogateTest.OldClass2" z:Assembly="NdcsSurrogateTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" xmlns="http://schemas.datacontract.org/2004/07/NdcsSurrogateTest">
  <_stable z:Id="2" z:Type="System.String" z:Assembly="0" xmlns="">Remains the same</_stable>
  <_x003C_OldProperty_x003E_k__BackingField z:Id="3" z:Type="System.Int32" z:Assembly="0" xmlns="">23</_x003C_OldProperty_x003E_k__BackingField>
</OldClass2>

When deserializing the first example using the NetDataContractSerializer with a custom binder to change the type and then either implementing ISerializable on that type or providing a surrogate selector which designates a serialization surrogate which basically fulfills the ISerializalbe role then you will see an empty SerializationInfo in the ISerializationSurrogate.SetObjectData method. When processing the xml in the second example the SerializationInfo seems to get the right information and things work as expected.

My conclusion is that the default XML produced by NetDataContractSerializer for types that support serialization only through the SerializableAttribute will not be compatible with deserialization using ISerializable or serialization surrogate techniques because of the lack of type information. So to make the use of NetDataContractSerializable more future proof, one should customize the serialization to ensure this type information is included in XML so that later deserialization can be customized without having the manually transform the source XML.

回答1:

Yep, you have to have a well thought out data migration path if you're using serialization. The solution you mentioned is what I would do personally, to include a check in the code that would deserialize. If an older version is detected, do a small transformation so it matches the new format, and proceed as needed. Once all of the data is transformed, that code can be obsoleted in a future release.