Versioning the hard way
-
Hello experts, even though I tried to create a minimal code sample, this question needs rather large code samples attached. Don't worry, all the interesting text is here, above all the code. An application has to serialize data to a file and to de-serialize from that file. This works like a charm. The working example is attached in the first two code samples. Now for the interesting part: the original working version is delivered to millions of satisfied customers. All their positive feedback encouraged the creation of version 2. Version 2 should of course be able to load data files from version 1. It is possible to change the data model without breaking compatibility, to some extend at least using
ObsoleteAttribute
andOptionalFieldAttribute
. But when changes get more fundamental, other approaches are to be researched. The third code sample shows an altered DataContainer class. In this example, it has to serialize one field that is not present in the original version. Therefore the well-known loading process fails. To recover from the exception, another assembly is loaded. In my test case, it's the original assembly. It's delivered within the version 2 project resources. The original assembly should load the legacy file. But it throws aTargetInvocationException
instead. Its inner exception tells us thatDataContainer
has three members while there are only two de-serialized. Now where does this legacy assembly know ofDataContainer
having three members nowadays? It should have called its integrated version ofDataContainer
, which then had two members and therefore should load the legacy file without moaning. However, here is the "minimal" working sample:// User Interface (unchanged throughout versions)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;namespace Test_Serialization_Versioning_Heavy
{
public partial class Form1 : Form
{
#region Declarationsstring \_filename = string.Empty; DataContainer \_dataContainer = new DataContainer(); #endregion #region Init public Form1() { \_filename = System.IO.Path.Combine(Application.StartupPath, "datafile.xml"); InitializeComponent(); ShowData(); } #endregion
-
Hello experts, even though I tried to create a minimal code sample, this question needs rather large code samples attached. Don't worry, all the interesting text is here, above all the code. An application has to serialize data to a file and to de-serialize from that file. This works like a charm. The working example is attached in the first two code samples. Now for the interesting part: the original working version is delivered to millions of satisfied customers. All their positive feedback encouraged the creation of version 2. Version 2 should of course be able to load data files from version 1. It is possible to change the data model without breaking compatibility, to some extend at least using
ObsoleteAttribute
andOptionalFieldAttribute
. But when changes get more fundamental, other approaches are to be researched. The third code sample shows an altered DataContainer class. In this example, it has to serialize one field that is not present in the original version. Therefore the well-known loading process fails. To recover from the exception, another assembly is loaded. In my test case, it's the original assembly. It's delivered within the version 2 project resources. The original assembly should load the legacy file. But it throws aTargetInvocationException
instead. Its inner exception tells us thatDataContainer
has three members while there are only two de-serialized. Now where does this legacy assembly know ofDataContainer
having three members nowadays? It should have called its integrated version ofDataContainer
, which then had two members and therefore should load the legacy file without moaning. However, here is the "minimal" working sample:// User Interface (unchanged throughout versions)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;namespace Test_Serialization_Versioning_Heavy
{
public partial class Form1 : Form
{
#region Declarationsstring \_filename = string.Empty; DataContainer \_dataContainer = new DataContainer(); #endregion #region Init public Form1() { \_filename = System.IO.Path.Combine(Application.StartupPath, "datafile.xml"); InitializeComponent(); ShowData(); } #endregion
You will have to make use of version tolerance: Version Tolerant Serialization[^].
0100000101101110011001000111001011101001
-
You will have to make use of version tolerance: Version Tolerant Serialization[^].
0100000101101110011001000111001011101001
Generally, that would be the good answer. As I already mentioned "...at least using
ObsoleteAttribute
andOptionalFieldAttribute
..." I am aware of the possibilities of Version Tolerant Serialization. But in this case, I need something more powerful. Hence my approach to load a legacy assembly and make it load the legacy file. Problem is, the legacy assembly throws an error on loading a file that it should be capable of loading. The error in turn refers to a data type that the legacy assembly cannot know of. How can I make this work?Ciao, luker
-
Hello experts, even though I tried to create a minimal code sample, this question needs rather large code samples attached. Don't worry, all the interesting text is here, above all the code. An application has to serialize data to a file and to de-serialize from that file. This works like a charm. The working example is attached in the first two code samples. Now for the interesting part: the original working version is delivered to millions of satisfied customers. All their positive feedback encouraged the creation of version 2. Version 2 should of course be able to load data files from version 1. It is possible to change the data model without breaking compatibility, to some extend at least using
ObsoleteAttribute
andOptionalFieldAttribute
. But when changes get more fundamental, other approaches are to be researched. The third code sample shows an altered DataContainer class. In this example, it has to serialize one field that is not present in the original version. Therefore the well-known loading process fails. To recover from the exception, another assembly is loaded. In my test case, it's the original assembly. It's delivered within the version 2 project resources. The original assembly should load the legacy file. But it throws aTargetInvocationException
instead. Its inner exception tells us thatDataContainer
has three members while there are only two de-serialized. Now where does this legacy assembly know ofDataContainer
having three members nowadays? It should have called its integrated version ofDataContainer
, which then had two members and therefore should load the legacy file without moaning. However, here is the "minimal" working sample:// User Interface (unchanged throughout versions)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;namespace Test_Serialization_Versioning_Heavy
{
public partial class Form1 : Form
{
#region Declarationsstring \_filename = string.Empty; DataContainer \_dataContainer = new DataContainer(); #endregion #region Init public Form1() { \_filename = System.IO.Path.Combine(Application.StartupPath, "datafile.xml"); InitializeComponent(); ShowData(); } #endregion
You may need to load the assembly into a separate AppDomain – but that then makes it impossible to copy the object across once it's loaded in the other domain, so it doesn't really help. With my lobby client (which loads its game type modules at runtime and does some version control on them) I had big problems trying to get two version of the same class to operate within one AppDomain. If the changes are more than just adding new fields, you might want to think about having the class unchanged and creating a new class which inherits from or wraps it. You might also want to look at the design: typically you should be serialising data, i.e. very simple data-containing objects, and I wouldn't expect them to change so much as to make the version tolerance in the framework insufficient. (The kind of thing you're experiencing here is one reason why I don't like to use [Serializable] for long term persistence, i.e. saving to file or other permanent storage. I typically have read/write methods on the persistent classes which convert them to the string or bytes that are needed to recreate each object – often not the same thing as its complete internal state, which is what serialisation saves – and those methods can handle the reading of prior versions, particularly if you pass as a parameter the version which the file claims to be.)