代码之家  ›  专栏  ›  技术社区  ›  Allon Guralnek

当存储的数据类型更改时,如何升级Settings.Settings?

  •  4
  • Allon Guralnek  · 技术社区  · 16 年前

    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
    

    新版本的类型是:

    public class Person
    {
        public string Name { get; set; }
        public DateTime DateOfBirth { get; set; }
    }
    

    显然, ApplicationSettingsBase.Upgrade (age) => DateTime.Now.AddYears(-age)

    因此,我想通过覆盖来提供一个升级例程

    1. ApplicationSettingsBase.GetPreviousVersion ,返回的值将是当前版本的对象,该对象没有Age属性,并且DateOfBirth属性为空(因为它无法将Age反序列化为DateOfBirths)。
    2. 我找不到一种方法来找出我要从哪个版本的应用程序升级。如果有从v1到v2的升级过程和从v2到v3的升级过程,如果用户从v1升级到v3,我需要按顺序运行这两个升级过程,但如果用户从v2升级,我只需要运行第二个升级过程。
    3. 即使我知道应用程序的先前版本是什么,并且我可以访问其先前结构中的用户设置(比如只获取一个原始XML节点),如果我想链接升级过程(如第2期所述),我应该在哪里存储中间值?如果从v2升级到v3,升级过程将从v2读取旧值,并将其直接写入v3中的强类型设置包装器类。但是,如果从v1升级,我应该把v1到v2升级过程的结果放在哪里,因为应用程序只有v3的包装器类?

    我以为如果升级代码直接在user.config文件上执行转换,我可以避免所有这些问题,但我发现没有简单的方法来获取以前版本的user.config的位置,因为 LocalFileSettingsProvider.GetPreviousConfigFileName(bool)

    是否有人有与ClickOnce兼容的解决方案来升级在应用程序版本之间更改类型的用户设置,最好是支持跳过版本的解决方案(例如,从v1升级到v3,而不需要用户安装v2)?

    3 回复  |  直到 16 年前
        1
  •  4
  •   Allon Guralnek    16 年前

    我最终使用了一种更复杂的方式进行升级,从用户设置文件中读取原始XML,然后运行一系列升级例程,将数据重构为下一个新版本中的预期方式。此外,由于我在ClickOnce的 ApplicationDeployment.CurrentDeployment.IsFirstRun 属性(您可以查看Microsoft Connect反馈 here ),我必须使用自己的IsFirstRun设置才能知道何时执行升级。整个系统对我来说运行得很好(但由于一些非常顽固的障碍,它是用血汗制成的)。忽略注释标记特定于我的应用程序且不属于升级系统的内容。

    using System;
    using System.Collections.Specialized;
    using System.Configuration;
    using System.Xml;
    using System.IO;
    using System.Linq;
    using System.Windows.Forms;
    using System.Reflection;
    using System.Text;
    using MyApp.Forms;
    using MyApp.Entities;
    
    namespace MyApp.Properties
    {
        public sealed partial class Settings
        {
            private static readonly Version CurrentVersion = Assembly.GetExecutingAssembly().GetName().Version;
    
            private Settings()
            {
                InitCollections();  // ignore
            }
    
            public override void Upgrade()
            {
                UpgradeFromPreviousVersion();
                BadDataFiles = new StringCollection();  // ignore
                UpgradePerformed = true; // this is a boolean value in the settings file that is initialized to false to indicate that settings file is brand new and requires upgrading
                InitCollections();  // ignore
                Save();
            }
    
            // ignore
            private void InitCollections()
            {
                if (BadDataFiles == null)
                    BadDataFiles = new StringCollection();
    
                if (UploadedGames == null)
                    UploadedGames = new StringDictionary();
    
                if (SavedSearches == null)
                    SavedSearches = SavedSearchesCollection.Default;
            }
    
            private void UpgradeFromPreviousVersion()
            {
                try
                {
                    // This works for both ClickOnce and non-ClickOnce applications, whereas
                    // ApplicationDeployment.CurrentDeployment.DataDirectory only works for ClickOnce applications
                    DirectoryInfo currentSettingsDir = new FileInfo(ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath).Directory;
    
                    if (currentSettingsDir == null)
                        throw new Exception("Failed to determine the location of the settings file.");
    
                    if (!currentSettingsDir.Exists)
                        currentSettingsDir.Create();
    
                    // LINQ to Objects for .NET 2.0 courtesy of LINQBridge (linqbridge.googlecode.com)
                    var previousSettings = (from dir in currentSettingsDir.Parent.GetDirectories()
                                            let dirVer = new { Dir = dir, Ver = new Version(dir.Name) }
                                            where dirVer.Ver < CurrentVersion
                                            orderby dirVer.Ver descending
                                            select dirVer).FirstOrDefault();
    
                    if (previousSettings == null)
                        return;
    
                    XmlElement userSettings = ReadUserSettings(previousSettings.Dir.GetFiles("user.config").Single().FullName);
                    userSettings = SettingsUpgrader.Upgrade(userSettings, previousSettings.Ver);
                    WriteUserSettings(userSettings, currentSettingsDir.FullName + @"\user.config", true);
    
                    Reload();
                }
                catch (Exception ex)
                {
                    MessageBoxes.Alert(MessageBoxIcon.Error, "There was an error upgrading the the user settings from the previous version. The user settings will be reset.\n\n" + ex.Message);
                    Default.Reset();
                }
            }
    
            private static XmlElement ReadUserSettings(string configFile)
            {
                // PreserveWhitespace required for unencrypted files due to https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=352591
                var doc = new XmlDocument { PreserveWhitespace = true };
                doc.Load(configFile);
                XmlNode settingsNode = doc.SelectSingleNode("configuration/userSettings/MyApp.Properties.Settings");
                XmlNode encryptedDataNode = settingsNode["EncryptedData"];
                if (encryptedDataNode != null)
                {
                    var provider = new RsaProtectedConfigurationProvider();
                    provider.Initialize("userSettings", new NameValueCollection());
                    return (XmlElement)provider.Decrypt(encryptedDataNode);
                }
                else
                {
                    return (XmlElement)settingsNode;
                }
            }
    
            private static void WriteUserSettings(XmlElement settingsNode, string configFile, bool encrypt)
            {
                XmlDocument doc;
                XmlNode MyAppSettings;
    
                if (encrypt)
                {
                    var provider = new RsaProtectedConfigurationProvider();
                    provider.Initialize("userSettings", new NameValueCollection());
                    XmlNode encryptedSettings = provider.Encrypt(settingsNode);
                    doc = encryptedSettings.OwnerDocument;
                    MyAppSettings = doc.CreateElement("MyApp.Properties.Settings").AppendNewAttribute("configProtectionProvider", provider.GetType().Name);
                    MyAppSettings.AppendChild(encryptedSettings);
                }
                else
                {
                    doc = settingsNode.OwnerDocument;
                    MyAppSettings = settingsNode;
                }
    
                doc.RemoveAll();
                doc.AppendNewElement("configuration")
                    .AppendNewElement("userSettings")
                    .AppendChild(MyAppSettings);
    
                using (var writer = new XmlTextWriter(configFile, Encoding.UTF8) { Formatting = Formatting.Indented, Indentation = 4 })
                    doc.Save(writer);
            }
    
            private static class SettingsUpgrader
            {
                private static readonly Version MinimumVersion = new Version(0, 2, 1, 0);
    
                public static XmlElement Upgrade(XmlElement userSettings, Version oldSettingsVersion)
                {
                    if (oldSettingsVersion < MinimumVersion)
                        throw new Exception("The minimum required version for upgrade is " + MinimumVersion);
    
                    var upgradeMethods = from method in typeof(SettingsUpgrader).GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
                                         where method.Name.StartsWith("UpgradeFrom_")
                                         let methodVer = new { Version = new Version(method.Name.Substring(12).Replace('_', '.')), Method = method }
                                         where methodVer.Version >= oldSettingsVersion && methodVer.Version < CurrentVersion
                                         orderby methodVer.Version ascending 
                                         select methodVer;
    
                    foreach (var methodVer in upgradeMethods)
                    {
                        try
                        {
                            methodVer.Method.Invoke(null, new object[] { userSettings });
                        }
                        catch (TargetInvocationException ex)
                        {
                            throw new Exception(string.Format("Failed to upgrade user setting from version {0}: {1}",
                                                              methodVer.Version, ex.InnerException.Message), ex.InnerException);
                        }
                    }
    
                    return userSettings;
                }
    
                private static void UpgradeFrom_0_2_1_0(XmlElement userSettings)
                {
                    // ignore method body - put your own upgrade code here
    
                    var savedSearches = userSettings.SelectNodes("//SavedSearch");
    
                    foreach (XmlElement savedSearch in savedSearches)
                    {
                        string xml = savedSearch.InnerXml;
                        xml = xml.Replace("IRuleOfGame", "RuleOfGame");
                        xml = xml.Replace("Field>", "FieldName>");
                        xml = xml.Replace("Type>", "Comparison>");
                        savedSearch.InnerXml = xml;
    
    
                        if (savedSearch["Name"].GetTextValue() == "Tournament")
                            savedSearch.AppendNewElement("ShowTournamentColumn", "true");
                        else
                            savedSearch.AppendNewElement("ShowTournamentColumn", "false");
                    }
                }
            }
        }
    }
    

    使用了以下自定义扩展方法和辅助类:

    using System;
    using System.Windows.Forms;
    using System.Collections.Generic;
    using System.Xml;
    
    
    namespace MyApp
    {
        public static class ExtensionMethods
        {
            public static XmlNode AppendNewElement(this XmlNode element, string name)
            {
                return AppendNewElement(element, name, null);
            }
            public static XmlNode AppendNewElement(this XmlNode element, string name, string value)
            {
                return AppendNewElement(element, name, value, null);
            }
            public static XmlNode AppendNewElement(this XmlNode element, string name, string value, params KeyValuePair<string, string>[] attributes)
            {
                XmlDocument doc = element.OwnerDocument ?? (XmlDocument)element;
                XmlElement addedElement = doc.CreateElement(name);
    
                if (value != null)
                    addedElement.SetTextValue(value);
    
                if (attributes != null)
                    foreach (var attribute in attributes)
                        addedElement.AppendNewAttribute(attribute.Key, attribute.Value);
    
                element.AppendChild(addedElement);
    
                return addedElement;
            }
            public static XmlNode AppendNewAttribute(this XmlNode element, string name, string value)
            {
                XmlAttribute attr = element.OwnerDocument.CreateAttribute(name);
                attr.Value = value;
                element.Attributes.Append(attr);
                return element;
            }
        }
    }
    
    namespace MyApp.Forms
    {
        public static class MessageBoxes
        {
            private static readonly string Caption = "MyApp v" + Application.ProductVersion;
    
            public static void Alert(MessageBoxIcon icon, params object[] args)
            {
                MessageBox.Show(GetMessage(args), Caption, MessageBoxButtons.OK, icon);
            }
            public static bool YesNo(MessageBoxIcon icon, params object[] args)
            {
                return MessageBox.Show(GetMessage(args), Caption, MessageBoxButtons.YesNo, icon) == DialogResult.Yes;
            }
    
            private static string GetMessage(object[] args)
            {
                if (args.Length == 1)
                {
                    return args[0].ToString();
                }
                else
                {
                    var messegeArgs = new object[args.Length - 1];
                    Array.Copy(args, 1, messegeArgs, 0, messegeArgs.Length);
                    return string.Format(args[0] as string, messegeArgs);
                }
    
            }
        }
    }
    

    [STAThread]
    static void Main()
    {
            // Ensures that the user setting's configuration system starts in an encrypted mode, otherwise an application restart is required to change modes.
            Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
            SectionInformation sectionInfo = config.SectionGroups["userSettings"].Sections["MyApp.Properties.Settings"].SectionInformation;
            if (!sectionInfo.IsProtected)
            {
                sectionInfo.ProtectSection(null);
                config.Save();
            }
    
            if (Settings.Default.UpgradePerformed == false)
                Settings.Default.Upgrade();
    
            Application.Run(new frmMain());
    }
    

        2
  •  1
  •   Simon P Stevens    16 年前

    这可能不是你真正想要的答案,但听起来你试图将其作为升级来管理,而不继续支持旧版本,这使问题过于复杂。

    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public DateTime DateOfBirth { get; set; }
    }
    

    当用户升级到新版本时,年龄仍然会被存储,因此当您访问DateOfBirth字段时,您只需检查是否存在DateOfBirths,如果不存在,则根据年龄计算并保存,以便下次访问时,它已经有出生日期,并且可以忽略年龄字段。

        3
  •  0
  •   sidney.andrews    16 年前

    我知道这个问题已经得到了回答,但我一直在考虑这个问题,并想添加一种处理自定义类型类似(不相同)情况的方法:

    public class Person
    {
    
        public string Name { get; set; }
        public int Age { get; set; }
        private DateTime _dob;
        public DateTime DateOfBirth
        {
            get
            {
                if (_dob is null)
                { _dob = DateTime.Today.AddYears(Age * -1); }
                else { return _dob; }     
            }
            set { _dob = value; }
        }
     }
    

    如果private_dob和public Age都为null或0,则会同时出现另一个问题。您始终可以将“出生日期”设置为“日期时间”。在这种情况下,今天是默认的。此外,如果你只知道一个人的年龄,你会如何告诉他们出生日期到当天?

    推荐文章