Question

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:

  • Still define a property ImageString in my class, marked as obsolete.
  • the property will only have a setter but no getter
  • 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.

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top