【问题标题】:Binary Formatter Custom Binding for Nested Class嵌套类的二进制格式化程序自定义绑定
【发布时间】:2021-01-19 18:08:21
【问题描述】:

所以我正在编写一个迁移工具来将一堆只是二进制格式对象的数据文件转换为我们漂亮的新 YAML 格式。问题是,从那时起,我们还对底层类进行了更改。所以我的计划是使用从源代码管理中获取的类的重命名版本来反序列化磁盘上的数据,将属性映射到新版本,然后将新版本序列化到 YAML。

所以“旧”类是这样的:

     [Serializable]
        public class OldQuestionnaire : INotifyPropertyChanged
        {
            private static Random random = new Random((int)DateTime.Now.Ticks);
            private bool synchronised;
    
            public OldQuestionnaire()
            {
                Questions = new List<Question>();
                QuestionnaireInstanceID = random.Next(0, int.MaxValue);
            }
    
            [field: NonSerialized]
            public event PropertyChangedEventHandler PropertyChanged;
    
            public string Customer { get; set; }
            public DateTime FilledDate { get; set; }
            public string FSR { get; set; }
            public bool IsPreferred { get; set; }
            public bool IsSiteSpecific { get; set; }
            public string JobRef { get; set; }
            public int QuestionnaireInstanceID { get; }
            public string QuestionnaireTemplateID { get; set; }
    
            //Database template ID
            public List<Question> Questions { get; set; }
    
            public DateTime ReleaseDate { get; set; }
            public string Site { get; set; }
    
            public bool Synchronised
            {
                get => synchronised;
                set
                {
                    synchronised = value;
                    OnPropertyChanged();
                }
            }
    
            public string TempStorageFilePath { get; set; }
            public string Title { get; set; }
    
            public void DeleteStoredCopy()
            {
                if (TempStorageFilePath == null || !File.Exists(TempStorageFilePath))
                {
                    return;
                }
                File.Delete(TempStorageFilePath);
            }
    
            public OldQuestionnaire GetBlankCopy()
            {
                OldQuestionnaire copy = new OldQuestionnaire
                {
                    Questions = Questions.Select(q => q.Clone()).Cast<Question>().ToList(),
                    Title = Title,
                    ReleaseDate = ReleaseDate,
                    QuestionnaireTemplateID = QuestionnaireTemplateID
                };
                return copy;
            }
    
            public void StoreToDisk(string tempPath)
            {
                string fullPath = Path.Combine(tempPath, $"{QuestionnaireInstanceID}.csat");
                TempStorageFilePath = fullPath;
                if (File.Exists(fullPath))
                {
                    File.Delete(fullPath);
                }
                BinaryFormatter formatter = new BinaryFormatter();
                using (Stream fStream = File.OpenWrite(fullPath))
                {
                    formatter.Serialize(fStream, this);
                }
            }
    
            protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }

注意,它以前只是被称为Questionnaire

所以迁移过程应该很简单:

            OldQuestionnaire oldQ;
            using (StreamReader reader = new StreamReader("../../../libcsatmigrations/BinaryToYAML1/ReferenceFiles/1753324844.csat"))
            {
                BinaryFormatter formatter = new BinaryFormatter {Binder = new OldQBinder()};
                oldQ = (OldQuestionnaire)formatter.Deserialize(reader.BaseStream);
            }
            BinaryToYAMLMigrator migrator = new BinaryToYAMLMigrator();
            string yaml = migrator.Migrate(oldQ);

migrator.Migrate(oldQ) 在哪里处理属性映射等。

最初,这引发了BinaryFormatter 找不到OldQuestionnaire 所需程序集的问题,因此我创建了一个自定义活页夹:

    public class OldQBinder:SerializationBinder
    {
        public override Type BindToType(string assemblyName, string typeName)
        {
            return typeName switch
                   {
                       "libcsatquestionnaire.Question" => typeof(Question),
                       "libcsatquestionnaire.Questionnaire" => typeof(OldQuestionnaire),
                       "libcsatquestionnaire.OneTenQuestion" => typeof(OneTenQuestion),
                       "libcsatquestionnaire.FreeCommentQuestion" => typeof(FreeCommentQuestion),
                        _ => null,
                   };
        }
    
    }

(顺便说一句,喜欢新的 C#8 模式)

这似乎已经解决了这个问题,但现在我遇到了一个非常相似的问题,它无法找到嵌套的自定义类型,所以我想活页夹只被调用一次。

System.Runtime.Serialization.SerializationException:无法加载 类型 System.Collections.Generic.List`1[[libcs​​atquestionnaire.Question, libcs​​atques...

System.Runtime.Serialization.SerializationException 无法加载 类型 System.Collections.Generic.List`1[[libcs​​atquestionnaire.Question, libcs​​atquestionnaire,版本=1.0.0.0,文化=中性, 反序列化需要 PublicKeyToken=null]]。在 System.Runtime.Serialization.ObjectManager.CompleteObject(ObjectHolder 持有人,布尔 bObjectFullyComplete) 在 System.Runtime.Serialization.ObjectManager.DoNewlyRegisteredObjectFixups(ObjectHolder 持有人)在 System.Runtime.Serialization.ObjectManager.RegisterObject(对象 obj, Int64 objectID, SerializationInfo info, Int64 idOfContainingObj, MemberInfo 成员,Int32[] arrayIndex) 在 System.Runtime.Serialization.Formatters.Binary.ObjectReader.RegisterObject(对象 obj, ParseRecord pr, ParseRecord objectPr, Boolean bIsString) at System.Runtime.Serialization.Formatters.Binary.ObjectReader.RegisterObject(对象 obj, ParseRecord pr, ParseRecord objectPr) 在 System.Runtime.Serialization.Formatters.Binary.ObjectReader.ParseObjectEnd(ParseRecord 公关)在 System.Runtime.Serialization.Formatters.Binary.ObjectReader.Parse(ParseRecord 公关)在 System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run()
在 System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler 处理程序,__BinaryParser serParser,布尔 fCheck,布尔 isCrossAppDomain, IMethodCallMessage methodCallMessage) 在 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(流 serializationStream, HeaderHandler 处理程序, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage) 在 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(流 serializationStream、HeaderHandler 处理程序、布尔 fCheck、 IMethodCallMessage methodCallMessage) 在 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(流 序列化流,HeaderHandler 处理程序,布尔 fCheck)在 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(流 序列化流,HeaderHandler 处理程序)在 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(流 序列化流)在 libcs​​atmigrations_tests.BinaryToYAMLMigratorTests.CanMigrateToYAML() 在 C:\Users\James H\Documents\repos\CSAT-Tooling\libcs​​atmigrations_tests\BinaryToYAMLMigratorTests.cs:line 21

所以,我的问题是,如何创建一个绑定器来反序列化这些嵌套类型?

【问题讨论】:

  • 只是部分开玩笑:我祈祷你的理智。老实说,在您拥有具有正确名称和命名空间(以及强名称/程序集标识)的类型版本之前,可能会更容易恢复,然后反序列化,然后使用 anything else 来存储数据i>,在点燃旧数据和旧类型之前,在它们燃烧的同时跳舞。
  • 不幸的是,旧数据分布在大约 100 台工程师笔记本电脑上。我真的不希望他们不得不处理一些 hacky 迁移,我只想给他们一个更新,为他们做这一切......最坏的情况,我会这样做。编写一个将所有内容通过 FTP 传输给我的脚本,我会处理它。关于我可以去哪里的任何想法?

标签: c# serialization binding


【解决方案1】:

注意:这可行,但不是解决根本问题的方法,更多的是可能对其他人有所帮助的解决方法。如果有人有实际答案,请发布。


我重新克隆了 repo,将 head 重置为在最后一个版本之前提交以获取所有正确的程序集并将其指向新的上游。遗憾的是,这将无法集成到原始项目中。

从这里开始,我将反序列化旧的二进制格式数据,并像在 YAML 中一样对其进行重新序列化。

            string              path           = Path.Combine(Environment.ExpandEnvironmentVariables("%appdata%"), "SE-CSAT","Stored");
            //Backup old files
            ZipFile backup = new ZipFile();
            backup.AddDirectory(path);
            backup.Save(new FileStream(path.Replace("\\Stored","backup.zip"),FileMode.Create));
            //Read and deserialise old files
            List<string>        files     = Directory.GetFiles(path,"*.csat").ToList();
            BinaryFormatter     formatter = new BinaryFormatter();
            List<Questionnaire> questionnaires = new List<Questionnaire>();
            foreach (string file in files)
            {
                using (FileStream fStream = new FileStream(file, FileMode.Open))
                {
                    questionnaires.Add((Questionnaire)formatter.Deserialize(fStream));
                }
            }
            ISerializer serializer = new SerializerBuilder()                           
                                    .WithTagMapping("tag:yaml.org,2002:OneTenQuestion",typeof(OneTenQuestion))
                                    .WithTagMapping("tag:yaml.org,2002:FreeCommentQuestion",typeof(FreeCommentQuestion))
                                    .Build();
            //Delete old files
            files.ForEach(File.Delete);
            //Reserialise as YAML
            List<string> yaml = questionnaires.Select(serializer.Serialize).ToList();
            //Write it all to disk
            string       newName;
            int          i = 0;
            foreach (string data in yaml)
            {
                do
                {
                    newName = Path.Combine(path, $"converted-{i++}.yml");
                } while (File.Exists(newName));
                File.WriteAllText(newName, data);
            }

然后,我将把这个可执行文件与我的更新包(它使用新的程序集)打包在一起,并在升级过程开始时运行它。迁移 YAML 是一个相当简单的映射过程。

为了防止程序集冲突,我将使用Fody/Costura 将这个小型可执行文件所需的程序集嵌入到二进制文件中。


更新:我也尝试过使用 Costura 将其编译到库中,希望编织可以防止从其他项目调用时发生装配冲突。遗憾的是,这不起作用,所以我坚持调用这个可执行文件。

我确实向 STDOUT 添加了一些输出,以便我可以将输出重定向到调用应用程序中的流并确定运行时发生的情况。

【讨论】:

  • 务实的策略 - 不错。
猜你喜欢
  • 1970-01-01
  • 2015-09-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-08
  • 2021-08-07
  • 2020-05-18
相关资源
最近更新 更多