-3

When a user uses my program, it will generate many thousands of objects over the course of a couple of hours. These cannot accumulate in RAM, so I want to write them to a single file as they are generated. Then, in a different program, the objects must all be deserialized.

When I try to serialize different objects of the same class to the same XML file and then try to deserialize later, I get:

System.InvalidOperationException: 'There is an error in XML document (1, 206).'

Inner Exception

XmlException: There are multiple root elements. Line 1, position 206.

Here is an example of a .NET 6.0 console app that recapitulates this problem:

using System.Xml.Serialization;
using System.IO;
using System.Xml;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // Serialize a person
            using (FileStream stream = new FileStream("people.xml", FileMode.Create))
            {
                Person jacob = new Person { Name = "Jacob", Age = 33, Alive = true };
                XmlSerializer serializer = new XmlSerializer(typeof(Person));
                serializer.Serialize(stream, jacob);
            }

            // Serialize another person to the same file using the "clean XML" method
            // https://stackoverflow.com/questions/1772004/how-can-i-make-the-xmlserializer-only-serialize-plain-xml
            using (StreamWriter stream = new StreamWriter("people.xml", true))
            {
                Person rebecca = new Person { Name = "Rebecca", Age = 45, Alive = true };
                stream.Write(SerializeToString(rebecca));
            }

            // Deserialize the people
            List<Person> people = new List<Person>();
            using (FileStream stream = new FileStream("people.xml", FileMode.Open))
            {
                XmlSerializer deserializer = new XmlSerializer(typeof(Person));
                while (stream.Position < stream.Length)
                {
                    people.Add((Person)deserializer.Deserialize(stream));
                }
            }

            // See the people
            foreach (Person person in people)
                Console.WriteLine($"Hello. I am {person.Name}. I am {person.Age} and it is {person.Alive} that I am alive.");
        }

        // Serialize To Clean XML
        public static string SerializeToString<T>(T value)
        {
            var emptyNamespaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });
            var serializer = new XmlSerializer(value.GetType());
            var settings = new XmlWriterSettings();
            settings.Indent = true;
            settings.OmitXmlDeclaration = true;

            using (var stream = new StringWriter())
            using (var writer = XmlWriter.Create(stream, settings))
            {
                serializer.Serialize(writer, value, emptyNamespaces);
                return stream.ToString();
            }
        }
    }

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

        public Person()
        {
            Name = "";
        }
    }
}
Ken White
  • 123,280
  • 14
  • 225
  • 444
srshuken
  • 1
  • 3
  • 1
    The xml file is not well formed which is allowed by the xml specification. Instead of FileStream you need to use XmlReader. XmlReaderSettings settings = new XmlReaderSettings(); settings.ConformanceLevel = ConformanceLevel.Fragment; XmlReader reader = XmlReader.Create("people.xml", settings); Then serializer.Serialize(reader, jacob); – jdweng Jul 25 '22 at 23:21

1 Answers1

0

Probably not the perfect answer, but could you de-serialize to a list, append your new person, and then re-serialize?

public static List<Person> ReadPeople(string file)
{
  var people = new List<Person>();

  if (File.Exists(file))
  {
    var serializer = new XmlSerializer(typeof(List<Person>));
    using (var stream = new FileStream(file, FileMode.Open))
    {
      people = (List<Person>)serializer.Deserialize(stream);
    }
  }

  return people;
}

public static void SavePerson(string file, Person person)
{
  var people = ReadPeople(file);
  people.Add(person);

  var serializer = new XmlSerializer(typeof(List<Person>));
  using (var stream = new FileStream(file, FileMode.Create))
  {
    serializer.Serialize(stream, people);
  }
}

Additionally, on the "FileMode.Create", is the "FileMode.Append" option. The problem with that is it will append lists next to each other, rather than nesting the "people"

NB - I've split this into two functions to give the flexibility of being able to load the file easily

Oyyou
  • 610
  • 5
  • 13