【问题标题】:Changing types during binary deserialization in C#在 C# 中的二进制反序列化期间更改类型
【发布时间】:2015-07-13 07:17:45
【问题描述】:

我们公司的一种解决方案使用了第三方服务。通信是通过 XML 消息传递完成的。最后,我们根据它们提供给我们的 XML 模式生成要使用的类,并在某些时候将其中一些类型序列化为数据库中的二进制 blob 以供以后使用。

问题在于该第 3 方公司已将其中一个字段从布尔类型更改为整数类型。现在,当我们尝试反序列化已经存在的数据时,我们可以预见会遇到类型转换异常(无法从布尔值转换为整数)。

我的问题是 - 我们如何使用旧的布尔类型反序列化数据库中的现有数据以将其转换为新的整数类型?

我尝试了很多方法,其中包括反射和实现 ISerializable,但到目前为止还没有成功。理想的解决方案是实现 ISerializable,但在尝试反序列化现有数据时出现“未找到成员”错误,因为它已经仅使用 Serializable 属性进行了序列化。

欢迎提出任何建议!

编辑:添加一些代码以清楚地展示我的问题。

namespace ClassLibrary
{
    [Serializable]
    public class Foo //: ISerializable
    {
        public bool Bar { get; set; }

        public Foo() { }

        //[OnDeserializing()]
        //internal void OnDeserializingMethod(StreamingContext context)
        //{
        //    Bar = 10;
        //}

        //public Foo(SerializationInfo info, StreamingContext context)
        //{
        //    Bar = (int)info.GetValue("Bar", typeof(int));
        //}

        //public void GetObjectData(SerializationInfo info, StreamingContext context)
        //{
        //    info.AddValue("Bar", Bar);
        //}
    }
}

namespace ConsoleApplication2
{
    static class Program
    {
        static void Main(string[] args)
        {
            Foo foo;

            // Run #1, where Foo.Bar is a boolean

            foo = new Foo();
            foo.Bar = true;
            SerializeObject(foo);
            byte[] data = File.ReadAllBytes(@".\Test.bin");
            foo = DeserializeObject(data) as Foo;

            // Now change Foo.Bar to an integer type, comment the lines above, and uncomment the two lines below
            //byte[] newData = File.ReadAllBytes(@".\Test.bin");
            //foo = DeserializeObject(newData) as Foo;

            Console.WriteLine(foo.Bar);
            Console.ReadLine();
        }

        private static Object DeserializeObject(byte[] buff)
        {
            if (buff == null) return null;
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Binder = new CustomSerializationBinder();
            MemoryStream ms = new MemoryStream(buff);
            return formatter.Deserialize(ms);
        }

        private static void SerializeObject(Object obj)
        {
            if (obj == null) return;
            BinaryFormatter formatter = new BinaryFormatter();
            using (FileStream ms = new FileStream(@".\Test.bin", FileMode.Create))
            {
                formatter.Serialize(ms, obj);
            }
        }
    }

【问题讨论】:

  • 您可以访问以前版本的 DLL 吗?
  • 你没有访问权限?
  • 字段名称有变化吗?还是他们保持相同的字段名称,只是改变了类型?这很重要,因为从序列化的角度来看,如果他们更改类型并保持相同的字段名称,这将是一个重大更改。理想情况下,他们会添加一个新的“int”字段,而单独留下旧的“bool”字段,并以某种方式将其标记为已弃用。如果他们这样做是一项重大更改,那么我认为您最好的选择是保留两个类的副本......一个用于反序列化旧数据,另一个用于反序列化新数据。
  • 您使用的是哪个序列化程序 -- XmlSerializerDataContractSerializer 或其他?
  • 你不能碰垃圾桶。序列化如果您的数据库现在有东西,您应该将 XML 反序列化到另一个类中 - 即使您无法让您的工具/框架为您执行此操作,您也可以始终以艰难的方式处理它(使用 System.Xml 并检查节点手动) - 下次最好将 Version 字段包含到您的二进制序列化类中,或者不要序列化为 Binary(使用 BinaryFormatter 或它的名称)

标签: c# serialization blob binaryformatter


【解决方案1】:

您可以使用ISerializable 处理这种情况,但是您需要使用SerializationInfo.GetEnumerator 遍历反序列化的属性以确定实际从流中读取的数据类型。另外,你需要注意BinaryFormatter序列化fields而不是properties,所以如果你的类使用Auto-Implemented Properties那么之前存储在二进制流中的名字将是name of the backing field 不是属性的名称。

例如,假设这是您的原始课程:

[Serializable]
public class Foo
{
    public bool Bar { get; set; }

    public Foo() { }
}

现在您想将Bar 更改为整数。要序列化或反序列化新旧BinaryFormatter 流,请使用ISerializable 的以下实现:

[Serializable]
public class Foo : ISerializable
{
    public int Bar { get; set; }

    public Foo() { }

    public Foo(SerializationInfo info, StreamingContext context)
    {
        var enumerator = info.GetEnumerator();
        while (enumerator.MoveNext())
        {
            var current = enumerator.Current;
            Debug.WriteLine(string.Format("{0} of type {1}: {2}", current.Name, current.ObjectType, current.Value));
            if (current.Name == "Bar" && current.ObjectType == typeof(int))
            {
                Bar = (int)current.Value;
            }
            else if (current.Name == "<Bar>k__BackingField" && current.ObjectType == typeof(bool))
            {
                var old = (bool)current.Value;
                Bar = (old ? 1 : 0); // Or whatever.
            }
        }
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("Bar", Bar);
    }
}

【讨论】:

  • 太棒了!我没有意识到 SerializationInfo 有一个枚举器来获取序列化字段。明天我将尝试实施。非常感谢,如果它有效,我已经为此撕掉了 3 天的头发!
  • 是的,这正是我所需要的。 SerializationInfo.GetEnumerator() 对我来说是谜题中缺失的环节。非常感谢您抽出宝贵的时间 dbc。你们是好人。
  • 这几乎正是我所需要的,谢谢。努力理解文档以及反序列化过程是如何工作的。愚蠢的问题,我知道,但这忽略了除 Bar 之外的任何其他属性......如何包含其他非类型转换的道具?
【解决方案2】:

如果我们将布尔值转换为整数,那将是 0 或 1。请您尝试一下:

int val=int.Tryparse(myBooleanValue);

或者

int val= myBooleanValue?1:0;

【讨论】:

  • 我认为您误解了这个问题。我了解如何将一个转换为另一个。问题是如何使用具有不同类型的新 dll 对数据库中已序列化的数据进行转换?
  • 是否要更改保存在数据库中的序列化数据中的数据类型
  • 如果我们更改json中的数据类型或序列化存储的数据,很难维护。您最好编写一个控制台应用程序,从 DB 中检索数据并反序列化,从 boolean 转换为 int 并将其按之前的序列化方式存储回 DB。我认为这可能是个坏主意。
  • 是的,你所描述的正是我想要做的。问题是 - 怎么做?如果我使用整数类型的 dll,而数据库具有布尔类型,我将无法再反序列化数据。
  • 编写作业(控制台应用程序)从数据库中检索所有序列化数据并对其进行反序列化。现在将数据类型从 boolean 更改为 integer 并将其序列化并将其存储回 DB。
猜你喜欢
  • 1970-01-01
  • 2013-06-14
  • 1970-01-01
  • 1970-01-01
  • 2011-06-08
  • 1970-01-01
  • 1970-01-01
  • 2016-07-27
  • 1970-01-01
相关资源
最近更新 更多