This is a common problem when de/re hydrating instances and changing the underlying type at the same time.
What you can do in these kind of situations is to write the equivalent of a header out along with the property information which can be used upon deserialization so that you can know which fields were actually serialized in the first place. The way that you 'tag' each field is entirely up to you, here I'm going to do it the most naive way just for demonstration purposes:
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("_Fields", 2);
info.AddValue("FieldA", FieldA);
info.AddValue("FieldB", FieldB);
}
protected MyObject(SerializationInfo info, StreamingContext context)
{
int fields = 0;
try{
fields = info.GetInt32("_Fields");
}
catch(Exception)
{
/* min known version kicks in instead, i.e.
before you added this field-tracking value */
}
if(fields!=0)
{
//if using an integer tracking field, you could also consider using
//bit flags - changing these tests to (fields & [fieldFlag]) == [fieldFlag]
//which would allow you to accommodate multiple combinations of fields more easily.
if(fields == 1)
FieldA = info.GetInt32("FieldA");
if(fields == 2)
FieldB = info.GetInt32("FieldB");
}
}
As the code suggests, when you roll-out this change, you should have a good knowledge of what the current mimum level of detail you can expect from the object data in your data stores, so you can still deserialize all the stuff that doesn't perform this.
You could take this a lot further and use strings to track the data that's present, making each string equal to the name of the field that is stored, and then employ some reflection-based solution that will automatically fill the fields. That's likely to be ugly code though (or, at least, I don't have enough time right now to provide an example!).