【问题标题】:How to store C# object data as XML using XML Serialization如何使用 XML 序列化将 C# 对象数据存储为 XML
【发布时间】:2017-10-22 06:16:13
【问题描述】:

有人可以建议我的设计是否可以实现 XML 序列化?

我已经为我的“系统管理”应用程序的核心功能编写了代码。对于我项目的 Sprint 1,我必须将所有数据存储在临时 XML 数据存储中,该存储将在 Sprint 2 中使用 ADO.Net 进行替换。

我已经研究过使用 XML 序列化对类进行序列化,例如以下文章: https://www.codeproject.com/Articles/483055/XML-Serialization-and-Deserialization-Part https://www.codeproject.com/Articles/487571/XML-Serialization-and-Deserialization-Part-2

我有以下类(我认为)需要存储为 XML:

  • 具有多个属性的用户类
  • 具有字符串属性名称和整数用户ID 列表的 UserAccessGroup 类
  • 一个 UserAdministration 类,包含所有用户对象和一个或多个 UserAccessGroup 对象的列表(构造函数使用参数 UserAccessGroup[])。它具有在系统上添加/删除用户、从 UserAccessGroup 添加/删除用户 ID 的方法。
  • 一个 UserVerification 类,它包含一个 UserAdministration 对象和正在登录的应用程序的 UserAccessGroup 对象。它还有两个用于计算失败和成功登录尝试的整数,一个用于登录的方法,如果满足所有条件,则返回 true,否则返回 false。
  • 具有多个属性的 ServiceRequest 类
  • 一个 ServiceRequestTracker 类,它包含所有 ServiceRequest 对象的列表,并具有添加/删除/编辑它们的方法。

我的程序文件中有以下代码:

static void Main()
        {
            UserAccessGroup SystemAdmin_App = new UserAccessGroup("Admin Operators");
            UserAccessGroup Shareholder_App = new UserAccessGroup("Shareholders");
            UserAccessGroup Broker_App = new UserAccessGroup("Brokers");
            UserAccessGroup StockExMgr_App = new UserAccessGroup("StockExMgrs");

            UserIDGenerator IDGenerator = new UserIDGenerator();

            UserAdministration userAdmin = new UserAdministration(IDGenerator,
                SystemAdmin_App, Shareholder_App, Broker_App, StockExMgr_App);

            UserVerificationService userVerification = new UserVerificationService(
                userAdmin, SystemAdmin_App);

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            LoginScreen myLoginScreen = new LoginScreen();
            Application.Run(myLoginScreen);
        }

现在我已经使用 TDD 完成了所有类的编写,我才开始研究将对象数据存储为 XML。我发现将 XML 序列化应用于我的设计非常令人困惑,我想知道是否可以在不从头开始完全重新设计我的应用程序的情况下实现这一点?

这里的任何帮助将不胜感激!

【问题讨论】:

  • 如果您使用现有的 XML 序列化程序,通常只需将 [Serializable] 属性放在一个类上并将您的对象传递给序列化程序;特别是如果您以后只打算让同一个应用程序反序列化对象。一个警告:您必须[XmlIgnore] 任何可能导致循环引用的属性。例如。如果 Parent 具有 Child 属性,并且该 Child 对象具有引用相同连接的 Parent 属性。您的序列化程序将陷入循环并导致堆栈溢出。
  • @Flater 感谢您的回复!我仍然不确定如何序列化我的对象。例如,UserAdministration 包含一个用户列表,并具有从列表中添加/删除用户的方法。例如,我通过调用 UserAdministration 的 addUser 方法添加一个新用户。新的 User 对象如何添加到 XML 文件中?
  • 只要根元素不是数组,任何一组类都可以被序列化。您的根元素是 userAdmin。所以你应该序列化 userAdmin,它会自动为其他类创建 xml。
  • @MichaelHennigan:我将添加一个答案,显示自定义类的基本序列化示例。如何存储数据(每个类类型的单独文件、一个大文件、每个父子家庭的单独文件……)完全取决于您。

标签: c# .net xml serialization deserialization


【解决方案1】:

根据 cmets 中的讨论,我将为您提供有关如何快速(反)序列化自定义类的基本概述。

作为参考,我正在回复您的评论:

@Flater 感谢您的回复!我仍然不确定如何序列化我的对象。例如,UserAdministration 包含一个用户列表,并具有从列表中添加/删除用户的方法。例如,我通过调用 UserAdministration 的 addUser 方法添加一个新用户。新的 User 对象如何添加到 XML 文件中?


我总是使用这个帮助类,因为它使代码更加简洁。我已经从某个地方的互联网上复制了它,可能是 StackOverflow。一旦我知道/记住了作者是谁,我就会相信作者。

public static class SerializerHelper
{
    /// <summary>
    /// Serializes an object.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="serializableObject"></param>
    /// <param name="fileName"></param>
    public static void SerializeObject<T>(string filepath, T serializableObject)
    {
        if (serializableObject == null) { return; }

        try
        {
            XmlDocument xmlDocument = new XmlDocument();
            XmlSerializer serializer = new XmlSerializer(serializableObject.GetType());
            using (MemoryStream stream = new MemoryStream())
            {
                serializer.Serialize(stream, serializableObject);
                stream.Position = 0;
                xmlDocument.Load(stream);
                xmlDocument.Save(filepath);
                stream.Close();
            }
        }
        catch (Exception ex)
        {
            //Log exception here
        }
    }


    /// <summary>
    /// Deserializes an xml file into an object list
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="fileName"></param>
    /// <returns></returns>
    public static T DeSerializeObject<T>(string filepath)
    {
        T objectOut = default(T);

        if (!System.IO.File.Exists(filepath)) return objectOut;

        try
        {
            string attributeXml = string.Empty;

            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.Load(filepath);
            string xmlString = xmlDocument.OuterXml;

            using (StringReader read = new StringReader(xmlString))
            {
                Type outType = typeof(T);

                XmlSerializer serializer = new XmlSerializer(outType);
                using (XmlReader reader = new XmlTextReader(read))
                {
                    objectOut = (T)serializer.Deserialize(reader);
                    reader.Close();
                }

                read.Close();
            }
        }
        catch (Exception ex)
        {
            //Log exception here
        }

        return objectOut;
    }
}

在我开始使用示例之前,一些提示让您知道如何处理:

  • 这些方法会将单个对象(任何类型)序列化到给定文件。这可以是类FooList&lt;Foo&gt;,或包含您要保存的所有数据的自定义类MyFooData
  • 您无法添加到现有文件。保存文件时,旧文件将被覆盖。我还没有遇到过需要添加到文件并且不能只读取文件内容、更改它们并再次存储整个对象的用例。但我只将它用于小规模存储。
  • XML 序列化使用公共属性无参数构造函数。因此,请确保您要存储的对象同时具有这两个。它不会序列化私有或受保护的属性;或公共/私有/受保护的类字段。
  • XML 序列化程序还将自动序列化其中包含的子类(如果它们是公共属性!)。据我所知,它可以深入到你想要的深度。它将保存一切
  • XML 序列化程序存在递归的弱点。我将在代码示例中解释如何避免递归。
  • XML 序列化无法(反)序列化Dictionary&lt;T1,T2&gt; 或任何使用哈希存储的IEnumerable。但是,如果您想存储 Dictionary&lt;T1,T2&gt;,您可以将其存储为 List&lt;KeyValuePair&lt;T1,T2&gt;&gt;,这意味着它将保留您的数据;只是不是用于快速查找的内部哈希。

1 - 最简单的例子

[Serializable]
public class User
{
    public string Name { get; set; }
    public string Email { get; set; }
}

public static void TestMethod()
{
    var myUser = new User() { Name = "John", Email = "John@john.com" };

    //Save to file
    SerializerHelper.SerializeObject(@"C:\MyDir\MyFile.txt", myUser);

    //Read from file
    var myUserReloaded = SerializerHelper.DeSerializeObject<User>(@"C:\MyDir\MyFile.txt");
}

注意User 类的[Serializable] 属性。这通常是您需要对类进行的唯一更改才能使其正常工作。

2 - 避免递归和堆栈溢出

如技巧中所述,这种序列化具有递归的弱点。当类在两个方向上相互引用时,就会发生这种情况。

[Serializable]
public class User
{
    public string Name { get; set; }
    public string Email { get; set; }

    public Manager Boss {get; set; }
}

[Serializable]
public class Manager
{
    public string Name { get; set; }

    public User FavoriteEmployee {get; set; }
}

public static void TestMethod()
{
    var userJohn = new User() { Name = "John", Email = "John@john.com" };
    var managerMark = new Manager() { Name = "Mark" };

    managerMark.FavoriteEmployee = userJohn;
    userJohn.Boss = managerMark;

    //Save to file
    SerializerHelper.SerializeObject(@"C:\MyDir\MyFile.txt", userJohn);

    //Read from file
    var userJohnReloaded = SerializerHelper.DeSerializeObject<User>(@"C:\MyDir\MyFile.txt");
}

保存文件时会出现堆栈溢出异常,因为序列化程序陷入无限循环。

序列化程序尝试将userJohn 的所有属性写入文件。当它到达Boss 属性时,它注意到它是一个可序列化的对象,并将开始序列化managerMark 的所有属性。当它到达FavoriteEmployee 属性时,它注意到它是一个可序列化的对象,并将开始序列化userJohn 的所有属性。当它...

您可以通过在两个属性之一上使用 [XmlIgnore] 属性来防止这种情况发生(或两者,如果您想真正安全的话)。

[Serializable]
public class User
{
    public string Name { get; set; }
    public string Email { get; set; }

    public Manager Boss {get; set; }
}

[Serializable]
public class Manager
{
    public string Name { get; set; }

    [XmlIgnore]
    public User FavoriteEmployee {get; set; }
}

序列化程序尝试将userJohn 的所有属性写入文件。当它到达Boss 属性时,它注意到它是一个可序列化的对象,并将开始序列化managerMark 的所有属性。当它到达FavoriteEmployee 属性时,它注意到该属性被标记为[XmlIgnore],并且它不会尝试序列化其中包含的内容。


我希望这个答案是您想要的。


编辑我忘记了一个重要的警告。

假设我们正在存储一个List&lt;Child&gt;,其中包含两个Child 对象。两个Child 对象都有一个Parent 属性,而且恰好两个子对象都引用同一个Parent(内存中的同一个对象)。

如果我存储Parent(包括其子项列表),它将序列化我们的Parent,其中将包含两个Child 对象。反序列化时,您将再次拥有一个单一的 Parent 对象和您开始使用的两个 Child 对象。

如果我存储了 List&lt;Child&gt;(包括它们的父级),它将为两个Child 对象序列化Parent。但是,在反序列化时,两个 Parent 对象都将被反序列化但它们将是内存中的单独对象

如您所见,如果您希望两个孩子都引用同一个Parent(作为内存中的对象),则此处可能存在潜在错误。出于这个原因,关于如何使用序列化,以及将[XmlIgnore] 属性放在哪里,我有一个个人规则。

孩子 (IEnumerable&lt;Foo&gt;) 会被序列化,父母 (Foo) 不会。这使我可以一次存储一整棵树。

这意味着我必须存储父级(并自动对其子级进行序列化),并且我只能存储子级而不引用其父级(除非您存储 @987654362 @ 键入Child)。

通过这种方式,您可以确保不会通过反序列化创建大量 Parent 对象,因为每个对象只会在 XML 文件中一次被提及。


编辑

我忘记在方法调用中添加SerializerHelper.。现已修复。

【讨论】:

  • 或者您可以尝试制作FavoriteEmployeeBoss ids 来指代使用用户ID。
  • @Tvde1:是的。但是,如果只存储boss的ID,那么还是需要单独序列化boss对象。而当再次加载数据时,您将不得不根据您存储的 ID 手动填写 Boss 属性。如果您仍然序列化对象(但只有一种方式,就像在我的示例中一样),那么反序列化将立即使用所需的对象及其数据填充 Boss 属性。我个人的规则是:孩子(IEnumerable 引用)被序列化,父母(单数引用)没有。这让我可以一次存储一整棵树。
  • @Flater 非常感谢您的回答,它真的很全面!我想我现在可以将它应用到我的应用程序中(可能需要一些试验和错误!)。
  • @MichaelHennigan:使用起来非常容易:) 我提到的警告是针对情况的,不会出现在简单的用例中。多年来,我只是一遍又一遍地使用相同的 sn-p 收集它们。
  • @Flater 在您的简单示例中,我想我必须首先创建一个 SerializeHelper 对象,然后调用 SerializeObject 方法,例如 MySerializeHelper.SerializeObject(@"C:\MyDir\MyFile.txt",用户约翰)?
猜你喜欢
  • 1970-01-01
  • 2018-10-11
  • 1970-01-01
  • 2014-11-30
  • 1970-01-01
  • 2011-12-03
  • 2019-03-04
  • 2018-05-25
相关资源
最近更新 更多