Reputation: 31743
Let's assume I have a simple class
public class Document
{
public int Version { get; set; }
public string Name { get; set; }
public string Image { get; set; } // Base64 coded Bitmap object
}
The real world object is way more complex. I use XmlSerializer.Serialize
to save instances to a file.
The content from image is generated this way:
byte[] result = null;
using (var image = Bitmap.FromFile(@"filename"))
using (var stream = new MemoryStream())
{
image.Save(stream, ImageFormat.Jpeg);
result = stream.ToArray();
}
var content = Convert.ToBase64String(result);
Now I have a breaking change. In the future I want to save the raw image Data (also as base64) without converting it to jpg.
So my New object will look like this:
public class Document
{
public int Version { get; set; }
public string Name { get; set; }
public string RawImageString { get; set; }
}
Luckily I already store a version attribute (currently 1
for every xml file). For new Items I can
Now I am wondering if there are any best practices
on how to deal with model changed.
I was thinking about this approach:
ImageString
in my class, marked as obsolete.If ImageString
is set I just update RawImageString
public class Document
{
public int Version { get; set; }
public string Name { get; set; }
public string RawImageString { get; set; }
[Obsolete("Use RawImageString instead")]
public string ImageString
{
set
{
this.RawImageString = value;
this.Version = 2;
}
}
}
This should work well, but it would require me to maintain the legacy property until forever. I would prefer
// depending on the version property XmlSerializer should return a
// different Document implementation
var serializer = new XmlSerializer(typeof(IDocument));
var document = (IDocument)serializer.Deserialize(reader);
Of course I could achive this with a factory method, but that would require two reads. One for the version the second for the concrete result.
Upvotes: 3
Views: 1004
Reputation: 31743
Eventually I solved it this way.
A Document
is created using a static method anyway.
Now I check if version matches the current version and start a migration if not.
public const int CURRENT_VERSION = 2;
public static DocumentOpen(string path)
{
var controller = new DocumentController();
var item = controller.ReadXml(path);
if (item.Version != CURRENT_VERSION)
{
var migrator = new DocumentMigrator(item, path);
migrator.MigrateToLatestVersion();
}
return item;
}
The migrator looks like this
public class DocumentMigrator
{
private Document item;
private String path;
public DocumentMigrator(Documentitem, string path)
{
this.item = item;
this.path = path;
}
public void MigrateToLatestVersion()
{
Migrate(Document.CURRENT_VERSION);
}
public void Migrate(int to)
{
Migrate(item.Version, to);
}
private void Migrate(int from, int to)
{
if (from < to)
{
while (item.Version < to)
Up(item.Version + 1);
}
else if (from > to)
{
while (item.Version < to)
Down(item.Version - 1);
}
}
private void Down(int version)
{
throw new NotImplementedException();
}
private void Up(int version)
{
if (version == 2)
{
var stream = File.OpenRead(path);
var serializer = new XmlSerializer(typeof(DocumentV1));
var document = (DocumentV1)serializer.Deserialize(stream);
this.item.RawImageString = document.ImageString;
}
else
{
throw new NotImplementedException();
}
this.item.Version = version;
}
}
public class DocumentV1
{
public string ImageString { get; set; }
}
The idea is that I created a helper class DocumentV1
which only contains the properties that I want to migrate. This could even be avoided by using dynamics or XElement.
I perform the upgrade and update the version from the original class. A Backward migration could also be implemented in the Down method but that is not required at the moment.
Upvotes: 2