Reputation: 31
I have been reading this https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/data-contract-versioning?redirectedfrom=MSDN article about data contract versioning.
It says that changing the name of the data member is a breaking change.
It then also says that if a data contract with a new field is deserialized into the old data contract(without the new field), it would work since the new field will be ignored. Also, old contract being deserialized into new contract would also work with the new field assigned a default value (assuming that the new field does not have IsRequired attribute, in which case it will throw an exception).
Now, lets say I have a data contract with a field called "MyField"(without IsRequired attribute) and I change the field name to "MyField1", that would be equivalent to deleting "MyField" and adding "MyField1". That should mean that if new contract is deserialized into old contract, "MyField" will be initialized to default value and vice versa.
So by breaking change, Microsoft mean that the field will not be set with correct values and not that it will throw an exception. Is my understanding correct?
Upvotes: 0
Views: 270
Reputation: 933
Hopefully, by breaking change Microsoft does not mean anything that you could not figure out using your own logic. However, everything depends on what you mean by “correct value”. The correctness of an evolution of a data contract depends on what you want to achieve.
To see it, let's review the entire cycle of moving from one version to another one.
Consider:
[DataContract(Namespace = "https:/www.my.site.org/namespaces.demo")]
class Demo {
internal Demo() { MyField = 13; }
[DataMember]
public int MyField { get; init; }
}
The only reason I've changed your fields to properties was to demonstrate the benefits of the init
access modifiers. Everything else would work on fields as well.
Use this type as a root of your data contract, write it to a file, and read from the same file. As expected, you will get MyField
with the value 13. Now, wipe out the member MyField
from our XML file and only read. You will no exception (as expected) and the value of 0. So, the data contract still worked on MyField
and gave you the default integer value, otherwise, you would get what is prescribed in the constructor, that is, 13. So, the constructor worked first, and then the value 13 assigned by the constructor was overwritten by the data contract. That's why I say it still works on this field.
Now, let's mark the old member with System.DeprecatedAttrubute
and add a new one, MyField1
:
[DataContract(Namespace = "https:/www.my.site.org/namespaces.demo")]
class Demo {
internal Demo() { MyPropety1 = "anything"; }
[DataMember, System.Obsolete]
public int MyProperty { get; init; }
[DataMember]
public string MyProperty1 { get; init; }
}
Isn't it surprising that the obsolete member MyProperty
is still taken by the data contract and placed in the output XML file (at least for the .NET version I'm using right now)? It makes perfect sense: the use of the attribute [System.Obsolete]
simply fails your build if you try to read the obsolete member explicitly or try to assign a value to it. It should not affect the behavior of data contracts, so, it deals with the obsolete fields the same way as with, say, private members. The data contract mechanism is based on Reflection and overcomes any access specifiers the same way as [System.Obsolete]
. Still, this attribute is very useful for the data contract migration.
Now, what happens if you completely remove the old MyPropery
from our data contract? You can do it by commenting out of all its attributes in the previous code sample, or, optionally, remove the member itself, with all the references. Still, nothing bad. The data perfectly reads, but, in this case, you will get the unmodified value "anything"
, as written in the class constructor. But I would say, you may or may not need it.
All you need is backward compatibility. When you migrate your software, the change involving removed data contract members is a non-incremental modification of the data contract, as opposed to the incremental change when you only add new members. You can do both, but with the non-incremental change, you face the following situation: you can have mixed XML data. Some files are created with the older software with the essential use of MyProperty
, and some — with the newer software without it.
In some cases, you don't care, but in many cases, it would mean lost data. And here is the place where the class System.Version
can help. You can add a member of this type to the root object of your data graph used by your data contract and introduce some version policy. I, for example, reserve the major version for non-incremental changes, and everything else for incremental changes only. This way, you reserve yourself room for reinterpreting your obsolete member read from the data created by older software versions. Obviously, the reinterpretation requires that you keep the old member and not mark it obsolete. In cases when you cannot ignore the obsolete data but need to reinterpret it, you have the possibility to read data, inspect the version member, and create an if
condition in your code using the version number. Of course, such things should be avoided by all means, but they give you a chance to work around a code design mistake created in the past.
Note that for using this technique, it would be best to have separate version schemas for your solution's assemblies and for data contracts.
Upvotes: 1