【问题标题】:Deserialize complex object using custom XmlSerialization使用自定义 XmlSerialization 反序列化复杂对象
【发布时间】:2015-04-09 10:56:31
【问题描述】:

我编写了以下示例代码,将一个稍微复杂的对象 FamilyTreeFile 保存到 XML 并将其恢复为原始形式。

public class XmlSerializationTest
{
    const string FileName = @"FamilyTree.xml";

    public void Run()
    {
        var rootMember = new Member() { Name = "Johny", Parent = null };
        var member1 = new Member() { Name = "Andy", Parent = rootMember };
        var member2 = new Member() { Name = "Adam", Parent = rootMember };
        var member3 = new Member() { Name = "Andrew", Parent = rootMember };
        var member4 = new Member() { Name = "Davis", Parent = member2 };
        var member5 = new Member() { Name = "Simon", Parent = member4 };

        rootMember.FamilyTree = new GenericCollection();
        rootMember.FamilyTree.Add(member1);
        rootMember.FamilyTree.Add(member2);
        rootMember.FamilyTree.Add(member3);
        member2.FamilyTree = new GenericCollection();
        member2.FamilyTree.Add(member4);
        member4.FamilyTree = new GenericCollection();
        member4.FamilyTree.Add(member5);

        var familyTree = new GenericCollection() { rootMember };

        IFamilyTreeFile file = new FamilyTreeFile()
        {
            FamilyTree = familyTree
        };

        Serialize(file);
        file = Deserialize();
    }

    public void Serialize(IFamilyTreeFile obj)
    {
        var xmlSerializer = new XmlSerializer(typeof(FamilyTreeFile));
        using (TextWriter writer = new StreamWriter(FileName))
        {
            xmlSerializer.Serialize(writer, obj);
        }
    }

    public IFamilyTreeFile Deserialize()
    {
        XmlSerializer serializer = new XmlSerializer(typeof(FamilyTreeFile));
        using (Stream stream = File.Open(FileName, FileMode.Open))
        {
            return (IFamilyTreeFile)serializer.Deserialize(stream);
        }
    }
}

public interface IMember
{
    string Name { get; set; }
    IMember Parent { get; set; }
    GenericCollection FamilyTree { get; set; }
}

[Serializable]
public class Member : IMember
{
    [XmlAttribute]
    public string Name { get; set; }
    [XmlIgnore]
    public IMember Parent { get; set; }
    public GenericCollection FamilyTree { get; set; }

    public Member()
    {
        //FamilyTree = new GenericCollection();
    }
}

[Serializable]
public class GenericCollection : List<IMember>, IXmlSerializable
{
    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        reader.MoveToContent();
        if (reader.Name == "FamilyTree")
        {
            do
            {
                reader.Read();
                if (reader.Name == "Member" && reader.IsStartElement())
                {
                    Type type = System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
                    .Where(x => x.Name == reader.Name)
                    .FirstOrDefault();
                    if (type != null)
                    {
                        var xmlSerializer = new XmlSerializer(type);
                        var member = (IMember)xmlSerializer.Deserialize(reader);
                        this.Add(member);
                    }
                }

                if (reader.Name == "FamilyTree" && reader.NodeType == System.Xml.XmlNodeType.EndElement)
                    break;
            }
            while (!reader.EOF);
        }
    }

    public void WriteXml(XmlWriter writer)
    {
        foreach (IMember rule in this)
        {
            var namespaces = new XmlSerializerNamespaces();
            namespaces.Add(String.Empty, String.Empty);
            XmlSerializer xmlSerializer = new XmlSerializer(rule.GetType());
            xmlSerializer.Serialize(writer, rule, namespaces);
        }
    }
}

public interface IFamilyTreeFile
{
    GenericCollection FamilyTree { get; set; }
}

public class FamilyTreeFile : IFamilyTreeFile
{
    public GenericCollection FamilyTree { get; set; }
}

代码示例正在生成以下 XML 文件,该文件完全符合我的需要,但我无法使用 ReadXml 方法将其读回。

<?xml version="1.0" encoding="utf-8"?>
<FamilyTreeFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <FamilyTree>
    <Member Name="Johny">
      <FamilyTree>
        <Member Name="Andy" />
        <Member Name="Adam">
          <FamilyTree>
            <Member Name="Davis">
              <FamilyTree>
                <Member Name="Simon" />
              </FamilyTree>
            </Member>
          </FamilyTree>
        </Member>
        <Member Name="Andrew" />
      </FamilyTree>
    </Member>
  </FamilyTree>
</FamilyTreeFile>

我需要有关如何有效恢复它的帮助?

添加

IMember 中添加新集合Notes

public interface IMember
{
    string Name { get; set; }
    IMember Parent { get; set; }
    GenericCollection FamilyTree { get; set; }
    List<Note> Notes { get; set; }
}

[Serializable]
public class Note
{
    [XmlAttribute]
    public string Text { get; set; }
}

Member 类中实现此属性

[XmlArray("Notes")]
public List<Note> Notes { get; set; }

我无法在这一行反序列化 Notes 信息。

var member = (IMember)xmlSerializer.Deserialize(reader);

难道没有任何简单的方法可以使用 XmlSerializer 或任何自己处理所有事情的框架进行反序列化吗?

【问题讨论】:

    标签: c# .net xml-serialization xmlserializer


    【解决方案1】:

    这是 GenericCollection.ReadXml 方法的工作版本:

    public void ReadXml(XmlReader reader)
    {
        // no need to advace upfront so MoveToContent was taken out (would 
        // mess with subsequent inner deserializations anyway)
    
        // very important: there may be no members, so check IsEmptyElement
        if (reader.Name == "FamilyTree" && !reader.IsEmptyElement) 
        {
            do
            {
                if (reader.Name == "Member" && reader.IsStartElement())
                {
                    Type type = System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
                                      .Where(x => x.Name == reader.Name)
                                      .FirstOrDefault();
                    if (type != null)
                    {
                        var xmlSerializer = new XmlSerializer(type);
                        var member = (IMember) xmlSerializer.Deserialize(reader);
                        this.Add(member);
                    }
                    continue; // to omit .Read because Deserialize did already 
                    // advance us to next element
                }
    
                if (reader.Name == "FamilyTree" && reader.NodeType == XmlNodeType.EndElement)
                    break;
    
                reader.Read();
            } while (!reader.EOF);
        }
    }
    

    您很可能在您的版本中错过了这样一个事实,即每次调用 XmlReader 的方法(如 Read...()Move...())都会提高它的阅读位置。成员对象的内部反序列化也是如此。 牢记这一点,应该清楚的是,您不能总是在循环开始时发出Read(),而只能在循环结束时发出。这样,您可以使用 continue 关键字跳过它,以防循环体中的一些其他代码(如我们的例子中的 Deserialize())确实已经推进了 XmlReader。同样适用于方法版本开头的MoveToContent()。我最初也错过了成员集合可以为空的事实。在这种情况下,必须完全省略 GenericCollection 的反序列化,因为(再次)不要弄乱读者。

    虽然这确实会反序列化对象实例并将它们添加到各自的列表中,但不会重建引用(本示例中 Member 类的 Parent 字段)。这就是事情变得棘手的地方:引用本质上是一个内存地址。既然如此,序列化它的价值并再次反序列化它是没有意义的。因为对象现在很可能驻留在另一个内存位置,所以反序列化的地址将完全错误。

    基本上有两种方法可以解决这个问题:

    1. 当对象被构造或粘合在一起时,序列化对象可以以自动创建这些引用的方式构造。这种方式根本不需要序列化和反序列化。缺点是:这仅适用于可以通过这种方式获得的引用(当前示例中就是这种情况)

    2. 每个可以作为引用冷目标的对象都可以通过标识符字段进行扩展,这与数据库中的主键非常相似。该标识符(例如 guid)随后将被序列化和反序列化。每个引用字段(本例中的 Member 类的 Parent 字段)都将被序列化为它所引用的对象的标识符值(可以通过添加帮助字段 ParentID 来完成,该字段由 Parent 字段的设置器自动设置) .当一切都被反序列化时,必须通过遍历整个对象树来重建这些引用。从好的方面来说,这使人们能够重建任意引用。但必须意识到这会增加代码的一些实际复杂性。

    第一种方法可以通过:

    在你的 Run() 函数中改变它...

    var rootMember = new Member() { Name = "Johny"};
    var member1 = new Member() { Name = "Andy" };
    var member2 = new Member() { Name = "Adam" };
    var member3 = new Member() { Name = "Andrew" };
    var member4 = new Member() { Name = "Davis" };
    var member5 = new Member() { Name = "Simon" };
    

    ...将成员类的属性 FamilyTree 更改为此...

    public GenericCollection FamilyTree
    {
        get { return _FamilyTree; }
        set
        {
            _FamilyTree = value;
            _FamilyTree.Owner = this;
        }
    }
    

    ...并将其插入到类 GenericCollection

    private IMember _Owner;
    public IMember Owner
    {
        get { return _Owner; }
        set
        {
            _Owner = value;
            foreach (var member in this)
            {
                member.Parent = value;
            }
        }
    }
    
    public void Add(IMember item)
    {
        item.Parent = Owner;
        base.Add(item);
    }
    

    第二种方法在以下小型控制台应用程序中实现:

    class Program
    {
        public static string FileName = @"FamilyTree.xml";
    
        static void Main(string[] args)
        {
            // make some members
            var rootMember = new Member() { Name = "Johny" };
            var member1 = new Member() { Name = "Andy" };
            var member2 = new Member() { Name = "Adam" };
            var member3 = new Member() { Name = "Andrew" };
            var member4 = new Member() { Name = "Davis" };
            var member5 = new Member() { Name = "Simon" };
    
            // construct some arbitrary references between them
            member1.Reference = member4;
            member3.Reference = member1;
            member5.Reference = member2;
    
            // let member 3 have some notes
            member3.Notes = new List<Note>();
            member3.Notes.Add(new Note() { Text = "note1" });
            member3.Notes.Add(new Note() { Text = "note2" });
    
            // add all of the to the family tree
            rootMember.FamilyTree.Add(member1);
            rootMember.FamilyTree.Add(member2);
            rootMember.FamilyTree.Add(member3);
            member2.FamilyTree.Add(member4);
            member4.FamilyTree.Add(member5);
    
            var familyTree = new GenericCollection() { rootMember };
    
            IFamilyTreeFile file = new FamilyTreeFile()
            {
                FamilyTree = familyTree
            };
    
            Console.WriteLine("--- input ---");
            Serialize(file);
            PrintTree(file.FamilyTree, 0);
            Console.WriteLine();
            Console.WriteLine("--- output ---");
            file = Deserialize();
            file.FamilyTree.RebuildReferences(file.FamilyTree); // this is where the refereces
            // are put  together again after deserializing the object tree.
            PrintTree(file.FamilyTree, 0);
            Console.ReadLine();
        }
    
        private static void PrintTree(GenericCollection c, int indent)
        {
            foreach (var member in c)
            {
                string line = member.Name.PadLeft(indent, ' ');
                if (member.Reference != null)
                {
                    line += " (Ref: " + member.Reference.Name + ")";
                    if (member.Notes != null && member.Notes.Count > 0)
                    {
                        line += " (Notes: ";
                        foreach (var note in member.Notes)
                        {
                            line += note.Text + ",";
                        }
                        line += ")";
                    }
                }
                Console.WriteLine(line);
                PrintTree(member.FamilyTree, indent + 4);
            }
        }
    
        public static void Serialize(IFamilyTreeFile obj)
        {
            var xmlSerializer = new XmlSerializer(typeof(FamilyTreeFile));
            using (TextWriter writer = new StreamWriter(FileName))
            {
                xmlSerializer.Serialize(writer, obj);
            }
        }
    
        public static IFamilyTreeFile Deserialize()
        {
            XmlSerializer serializer = new XmlSerializer(typeof(FamilyTreeFile));
            using (Stream stream = File.Open(FileName, FileMode.Open))
            {
                return (IFamilyTreeFile)serializer.Deserialize(stream);
            }
        }
    }
    
    public interface IMember
    {
        Guid ID { get; set; }
        string Name { get; set; }
        IMember Reference { get; set; }
        Guid ReferenceID { get; set; }
        GenericCollection FamilyTree { get; set; }
        List<Note> Notes { get; set; }
        void RebuildReferences(GenericCollection in_Root);
    }
    
    [Serializable]
    public class Member : IMember
    {
        private GenericCollection _FamilyTree;
        private IMember _Reference;
    
        [XmlAttribute]
        public Guid ID { get; set; }
        [XmlAttribute]
        public string Name { get; set; }
        [XmlAttribute]
        public Guid ReferenceID { get; set; }
        [XmlIgnore]
        public IMember Reference
        {
            get { return _Reference; }
            set
            {
                ReferenceID = value.ID;
                _Reference = value;
            }
        }
        [XmlArray("Notes")]
        public List<Note> Notes { get; set; }
    
        public GenericCollection FamilyTree
        {
            get { return _FamilyTree; }
            set
            {
                _FamilyTree = value;
                _FamilyTree.Owner = this;
            }
        }
    
        public Member()
        {
            ID = Guid.NewGuid();
            FamilyTree = new GenericCollection();
        }
    
        public void RebuildReferences(GenericCollection in_Root)
        {
            if (!ReferenceID.Equals(Guid.Empty))
                Reference = in_Root.FindMember(ReferenceID);
    
            FamilyTree.RebuildReferences(in_Root);
        }
    }
    
    [Serializable]
    public class Note
    {
        [XmlAttribute]
        public string Text { get; set; }
    }
    
    [Serializable]
    public class GenericCollection : List<IMember>, IXmlSerializable
    {
        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }
    
        private IMember _Owner;
        public IMember Owner
        {
            get { return _Owner; }
            set
            {
                _Owner = value;
            }
        }
    
        public void Add(IMember item)
        {
            base.Add(item);
        }
    
        public void ReadXml(XmlReader reader)
        {
            // no need to advace upfront so MoveToContent was taken out (would 
            // mess with subsequent inner deserializations anyway)
    
            // very important: there may be no members, so check IsEmptyElement
            if (reader.Name == "FamilyTree" && !reader.IsEmptyElement)
            {
                do
                {
                    if (reader.Name == "Member" && reader.IsStartElement())
                    {
                        Type type = System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
                                          .Where(x => x.Name == reader.Name)
                                          .FirstOrDefault();
                        if (type != null)
                        {
                            var xmlSerializer = new XmlSerializer(type);
                            var member = (IMember)xmlSerializer.Deserialize(reader);
                            this.Add(member);
                        }
                        continue; // to omit .Read because Deserialize did already 
                        // advance us to next element
                    }
    
                    if (reader.Name == "FamilyTree" && reader.NodeType == XmlNodeType.EndElement)
                        break;
    
                    reader.Read();
                } while (!reader.EOF);
            }
        }
    
        public void WriteXml(XmlWriter writer)
        {
            foreach (IMember rule in this)
            {
                var namespaces = new XmlSerializerNamespaces();
                namespaces.Add(String.Empty, String.Empty);
                XmlSerializer xmlSerializer = new XmlSerializer(rule.GetType());
                xmlSerializer.Serialize(writer, rule, namespaces);
            }
        }
    
        public void RebuildReferences(GenericCollection in_Root)
        {
            foreach (IMember meber in this)
            {
                meber.RebuildReferences(in_Root);
            }
        }
    
        public IMember FindMember(Guid in_ID)
        {
            IMember FoundMember = null;
            foreach (IMember member in this)
            {
                if (member.ID.Equals(in_ID))
                    return member;
    
                FoundMember = member.FamilyTree.FindMember(in_ID);
                if (FoundMember != null)
                    return FoundMember;
            }
            return null;
        }
    }
    
    public interface IFamilyTreeFile
    {
        GenericCollection FamilyTree { get; set; }
    }
    
    public class FamilyTreeFile : IFamilyTreeFile
    {
        public GenericCollection FamilyTree { get; set; }
    }
    

    在第二个示例中公开了您添加到原始问题的概念证明。

    【讨论】:

    • 感谢您提供指导,但我如何检索由于序列化错误而忽略的父信息?也请帮忙。
    • 我有两种不同的可能方法。请耐心等待我需要一些时间来实施和测试。完成后我会编辑我的答案...
    • 感谢您的努力,非常感谢,但请抽出时间用第二种方法更新您的答案。它不仅可以帮助我学习,而且其他人可能会发现它很有帮助。
    • @Perter,非常感谢,我也会评估第二种方法,但是当我向 IMember 添加一个新集合时,我仍然面临反序列化的一个问题,比如 List 类型的注释,其中 Note 有一个名为 Text 的字符串类型的属性。在这方面,我在我的问题中添加了一个新块。
    • 我刚刚更正了原始答案代码中的一个错误。现在一切都应该好了。
    猜你喜欢
    • 1970-01-01
    • 2014-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-09
    • 1970-01-01
    • 2014-05-20
    相关资源
    最近更新 更多