Sana Assistant (online)
Table of Contents

What you can do in extension upgrades

We assume that you've already passed at least one of the upgrade tutorials so that you are already familiar with this concept of 'old model snapshot + new model snapshot + upgrade class'. If not, please consider going through those tutorials first.

Supported

Changing existing property value

You can change the value of existing property in your model class. For example, assuming you have the 'old' and the 'new' model snapshot classes:

'Old' model:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_0
{
    public class TutorialConfiguration
    {
        public string SomeConfigurationValue { get; set; }
    }
}

'New' model:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_1
{
    public class TutorialConfiguration
    {
        public string SomeConfigurationValue { get; set; }
    }
}

They both have the same property, and you can change the value of the property during upgrade.

Upgrade implementation:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_1
{
    public class TutorialConfigurationUpgrade
        : ConfigurationUpgrade<v1_0_0.TutorialConfiguration, TutorialConfiguration>
    {
        /* some code omitted for clarity */

        public override void UpgradeConfiguration(v1_0_0.TutorialConfiguration oldModel, TutorialConfiguration newModel)
        {
            newModel.SomeConfigurationValue = oldModel.SomeConfigurationValue + " - the value has been upgraded to 1.0.1";
        }
    }
}

New property added

When you add a new property to the model, you simply add it to your 'new' model snapshot too and then you may or may not pre-set some default value for it during the upgrade.

'Old' model:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_0
{
    public class TutorialConfiguration
    {
        public string SomeConfigurationValue { get; set; }
    }
}

'New' model:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_1
{
    public class TutorialConfiguration
    {
        public string SomeConfigurationValue { get; set; }
        
        // new property added in new version
        public int AnotherConfigurationValue { get; set; }
    }
}

Upgrade implementation:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_1
{
    public class TutorialConfigurationUpgrade
        : ConfigurationUpgrade<v1_0_0.TutorialConfiguration, TutorialConfiguration>
    {
        /* some code omitted for clarity */

        public override void UpgradeConfiguration(v1_0_0.TutorialConfiguration oldModel, TutorialConfiguration newModel)
        {
            // you can pre-set some value, or skip it if not needed
            newModel.AnotherConfigurationValue = 42;
        }
    }
}

Old property removed

If some property is removed from the model, you should keep it in 'old' model snapshot class, but do not keep it in the 'new' model class.

'Old' model:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_0
{
    public class TutorialConfiguration
    {
        public string SomeConfigurationValue { get; set; }

        // some property, that existed before
        public int AnotherConfigurationValue { get; set; }
    }
}

'New' model:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_1
{
    public class TutorialConfiguration
    {
        public string SomeConfigurationValue { get; set; }
        
        // 'AnotherConfigurationValue' property was removed
    }
}

Upgrade implementation:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_1
{
    public class TutorialConfigurationUpgrade
        : ConfigurationUpgrade<v1_0_0.TutorialConfiguration, TutorialConfiguration>
    {
        /* some code omitted for clarity */

        public override void UpgradeConfiguration(v1_0_0.TutorialConfiguration oldModel, TutorialConfiguration newModel)
        {
            // work with other properties, 'AnotherConfigurationValue' is not in the new model anymore
        }
    }
}

Property renamed

If you decide to rename some property in your model between different add-on versions, then you'll have keep the old property name in the 'old' model snapshot, and keep the new property name in the 'new' model snapshot class. For Sana renaming property is the same as if you added a new one and removed an old one at the same time.

'Old' model:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_0
{
    public class TutorialConfiguration
    {
        // old property name
        public string SomeConfigurationValue { get; set; }
    }
}

'New' model:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_1
{
    public class TutorialConfiguration
    {
        // this is the same 'SomeConfigurationValue' property but renamed to 'AnotherPropertyName'
        public string AnotherPropertyName { get; set; }
    }
}

Upgrade implementation:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_1
{
    public class TutorialConfigurationUpgrade
        : ConfigurationUpgrade<v1_0_0.TutorialConfiguration, TutorialConfiguration>
    {
        /* some code omitted for clarity */

        public override void UpgradeConfiguration(v1_0_0.TutorialConfiguration oldModel, TutorialConfiguration newModel)
        {
            // you have to copy your old property to the new one
            newModel.AnotherPropertyName = oldModel.SomeConfigurationValue;
        }
    }
}

Attributes on properties

You may have data annotation attributes or other attributes decorating your model's properties. General rule is that when you copy your properties from your current model class to 'old' or 'new' model snapshot class, you should copy your properties together with their attributes. The attributes applied to the model class itself are not needed, only properties matter.

Let's go through some specific examples.

Attributes not changed between versions

Let's say, below is your model class, as it was when you released v1.0.0 of the add-on. Since attributes on properties did not change between versions, the model class will be the same when you release version 1.0.1 of the add-on.

namespace Sana.Extensions.UpgradesTutorial
{
    [ConfigurationKey("UpgradesTutorial")]
    public class TutorialConfiguration : ExtensionConfiguration
    {
        [Display(Name = "SomeConfigurationValue")]
        [SecureString]
        public string SomeConfigurationValue { get; set; }
    }
}

So when you create 'old' and 'new' snapshot classes for your model, simply copy properties together with their attributes. Don't copy class attributes.

'Old' model:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_0
{
    public class TutorialConfiguration
    {
        [Display(Name = "SomeConfigurationValue")]
        [SecureString]
        public string SomeConfigurationValue { get; set; }
    }
}

'New' model:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_1
{
    public class TutorialConfiguration
    {
        [Display(Name = "SomeConfigurationValue")]
        [SecureString]
        public string SomeConfigurationValue { get; set; }
    }
}

New attribute added

Your model class, as it was when you released v1.0.0 of the add-on:

namespace Sana.Extensions.UpgradesTutorial
{
    [ConfigurationKey("UpgradesTutorial")]
    public class TutorialConfiguration : ExtensionConfiguration
    {
        [Display(Name = "SomeConfigurationValue")]
        [SecureString]
        public string SomeConfigurationValue { get; set; }
    }
}

In the new version you've added some attribute to the property, for example, you want to change your property to be mandatory in the new version, so you apply Required attribute to it:

Your current model class, as it is now in v1.0.1 of the add-on:

namespace Sana.Extensions.UpgradesTutorial
{
    [ConfigurationKey("UpgradesTutorial")]
    public class TutorialConfiguration : ExtensionConfiguration
    {
        [Required(ErrorMessageResourceName = RequiredKey)] // new attribute added
        [Display(Name = "SomeConfigurationValue")]
        [SecureString]
        public string SomeConfigurationValue { get; set; }
    }
}

Once again, when you create 'old' and 'new' snapshot classes you simply copy properties together with their attributes.

'Old' model:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_0
{
    public class TutorialConfiguration
    {
        [Display(Name = "SomeConfigurationValue")]
        [SecureString]
        public string SomeConfigurationValue { get; set; }
    }
}

'New' model:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_1
{
    public class TutorialConfiguration
    {
        [Required(ErrorMessageResourceName = RequiredKey)] // new attribute added here as well
        [Display(Name = "SomeConfigurationValue")]
        [SecureString]
        public string SomeConfigurationValue { get; set; }
    }
}

Attribute removed

You may also remove some attribute from the model's property between releases.

Your model class, as it was when you released v1.0.0 of the add-on:

namespace Sana.Extensions.UpgradesTutorial
{
    [ConfigurationKey("UpgradesTutorial")]
    public class TutorialConfiguration : ExtensionConfiguration
    {
        [Required(ErrorMessageResourceName = RequiredKey)]
        [Display(Name = "SomeConfigurationValue")]
        [SecureString]
        public string SomeConfigurationValue { get; set; }
    }
}

Now, opposite to the previous example, let's say you want to make the property to be not mandatory, so you remove Required attribute from it in the next version.

Your current model class, as it is now in v1.0.1 of the add-on:

namespace Sana.Extensions.UpgradesTutorial
{
    [ConfigurationKey("UpgradesTutorial")]
    public class TutorialConfiguration : ExtensionConfiguration
    {
        // 'Required' attribute is gone now
        [Display(Name = "SomeConfigurationValue")]
        [SecureString]
        public string SomeConfigurationValue { get; set; }
    }
}

Same thing as in the examples above, when you create 'old' and 'new' snapshot classes you simply copy properties together with their attributes.

'Old' model:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_0
{
    public class TutorialConfiguration
    {
        [Required(ErrorMessageResourceName = RequiredKey)] // 'old' model snapshot still has the attribute
        [Display(Name = "SomeConfigurationValue")]
        [SecureString]
        public string SomeConfigurationValue { get; set; }
    }
}

'New' model:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_1
{
    public class TutorialConfiguration
    {
        // 'Required' attribute is gone in 'new' model
        [Display(Name = "SomeConfigurationValue")]
        [SecureString]
        public string SomeConfigurationValue { get; set; }
    }
}

Not supported

Changed type of property

You cannot change type of property in your data models. Unfortunately, Sana cannot distinguish the type difference in such scenario and will throw an exception. Instead you should create a new property with a new name in your 'new' model snapshot class.

'Old' model:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_0
{
    public class TutorialConfiguration
    {
        public string SomeConfigurationValue { get; set; }
    }
}

Not supported - 'New' model with changed property type:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_1
{
    public class TutorialConfiguration
    {
        // this is not supported
        public int SomeConfigurationValue { get; set; }
    }
}

Use instead - 'New' model with a new property:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_1
{
    public class TutorialConfiguration
    {
        // new property with a different name and type
        public int NewConfigurationValue { get; set; }
    }
}

Upgrade implementation:

namespace Sana.Extensions.UpgradesTutorial.Upgrades.v1_0_1
{
    public class TutorialConfigurationUpgrade
        : ConfigurationUpgrade<v1_0_0.TutorialConfiguration, TutorialConfiguration>
    {
        /* some code omitted for clarity */

        public override void UpgradeConfiguration(v1_0_0.TutorialConfiguration oldModel, TutorialConfiguration newModel)
        {
            // you have to copy your old property to the new one
            newModel.NewConfigurationValue = int.Parse(oldModel.SomeConfigurationValue);
        }
    }
}