Adding Versioning Information
As your program matures over time and you create newer versions, you’re probably going to need to handle different versions of files. If you have new features, you might want to write data to your file that supports those new features.
Generally speaking, when you create a new version of your program, you’ll want to be able to open any version of the data file up to the current version. For example, version 2.0 of your program should be able to open files saved with version 1.0 or 2.0. Version 3.0 should be able to open files saved with versions 1.0, 2.0, or 3.0.
As for dealing with future versions, that’s your call. For example, should version 1.0 be able to open files saved with version 3.0? Certainly 1.0 won’t be able to handle the data for the newer features, since 1.0 doesn’t have the newer features found in 3.0. You have several possibilities:
- Refuse to open future versions, providing a friendly message stating that the file is from a newer version and asking that the user upgrade.
- Open the file anyway, ignoring the newer features. When the file is resaved, the data from the newer features will be thrown out. (You would want to provide a warning message to the user before saving, however.)
- Open the file anyway, save the data from the newer features aside, and rewrite them back to the file.
Of these, the first option is the easiest to implement, and the third is by far the most difficult to implement. Imagine that in version 3.0, you create a new class for a new feature, and save an instance of that class to the file. If you try to load the file with version 1.0 of the software (which doesn’t have the new class), the serialization library won’t be able to locate that class, and you’ll get an exception error. In a moment I’ll give you some tips how to work around this problem and make the third option work. But first let’s add the version information to the file.
To add a version number, simply write out the number before you write your data. You can use the Serialize method again:
where ProgramVersion is a constant in your program for the version. (I used an Int32 for mine.) Then, when you read in the data, first read in the version so you know what file version you’re encountering. Do this before reading the rest of the data, since the version comes first in the stream:
Dim version As Int32 version = CType(readformatter.Deserialize(readstream), Int32) If version > ProgramVersion Then ’ ... handle situation of future versions End If
Next, you have to make a commitment to yourself that as you develop future versions of your program, you’ll continue with this same approach of writing the version number first. That way, version 3.0 will write the version number first, and if somebody uses version 1.0 to open a file saved with version 3.0, the first thing read will be the version number. Version 1.0 of the software will see that the file version is greater than the current version, and can deal with it in the "handle situation" remark I put in the preceding code.
Now let’s talk about how to handle the situation.
The first option is not to allow the user to open the file. That’s simply a matter of displaying a message and exiting the routine:
If version > ProgramVersion Then ’ ... handle situation of future versions MsgBox("This file was saved with a newer version. " & _ "You can find information on our web site for upgrading at http://www...") Return End If
For the second possibility, you can load the file anyway, but ignore the newer data. To accomplish that goal, however, you need to divide your data into version information. This may or may be feasible, depending on your particular program. One way to do it is to create new classes to hold the new features. Then you would write out the data broken up by version. Here’s some code that does this (for the version numbers, I’m using 1000 for 1.0.00 and 2000 for 2.0.00):
Dim formatter As New _ Runtime.Serialization.Formatters.Binary.BinaryFormatter Dim stream As New FileStream("c:\temp\userdata.dat", _ FileMode.Create, FileAccess.Write, FileShare.None) formatter.Serialize(stream, 1000) formatter.Serialize(stream, UserData1000) ’ 1.0.00 data formatter.Serialize(stream, 2000) formatter.Serialize(stream, UserData) ’ 2.0.00 data stream.Close()
The code in version 1.0 of the software would look the same, but with only the 1000 section, just as before. Version 3.0 of the software would write three sets. All versions, including version 1.0, would check the version before reading each block of data and only read up to their own data. Here’s the code that would accomplish this trick:
Dim done As Boolean = False HaveFutureData = False While Not done Try Dim version As Int32 version = CType(readformatter.Deserialize(readstream), Int32) If version > ProgramVersion Then ’ Ignore the future data, but note that we have it. HaveFutureData = True Else readdata = CType(readformatter.Deserialize(readstream), ArrayList) ProcessData(readdata) ’ ... some function that processes the data End If Catch ex As Exception done = True End Try End While readstream.Close()
The variable HaveFutureData would be a member variable or perhaps a global variable. ProcessData would be a function you write that processes the incoming data. Thus, all the data that can be read would be read; data from future versions would be ignored.
Elsewhere in your code, when you save the data, you would want to check the value of HaveFutureData. If it’s True, you’ll want to warn the user that the file originally contained newer data and the newer data will be lost if you save. Also, you might provide a message when opening the file, warning the user that newer data will not be read in, and that rewriting will result in a loss of data.
As for the third possibility, where you read in the newer data anyway and save it aside—I’m not going to attempt to write that code here. In fact, I probably wouldn’t include such a feature, because it likely would be more work than it’s worth. Also, the code wouldn’t be fun. You’d have to open a BinaryReader stream on top of the FileStream you’re reading. If the version of data being read was higher than the current version, instead of calling Deserialize you would read in the block of data by using the BinaryReader’s read method. However, you’d need to figure out how long the data was, so you would know how much to read in. Then you’d have to save the raw data away in a structure, and write it back out later on when the file was saved, along with the version number. Personally, I don’t think it’s worth all that effort. You’re free to try it if you want, though.