使用 C# XmlSerializer 为大型对象集写入块以避免内存不足

Use C# XmlSerializer to write in chunks for large sets of objects to avoid Out of Memory

我喜欢 XmlSerialize 的工作方式,如此简单优雅并带有属性 =p 但是,在序列化为 [=19 之前构建所有对象的集合时,我 运行 陷入内存不足问题=] 文件。

我正在从 SQL 数据库填充一个对象,并打算使用 XmlSerialize 将该对象写出到 XML。它适用于小子集,但如果我尝试从数据库中获取所有对象,我会遇到内存不足异常。

XmlSerialize 是否有某种功能允许我从数据库中抓取 100 个对象批次,然后写入它们,抓取下一批 100 个对象并附加到 xml?

我希望我不必闯入 XmlDocument 或需要更多手动编码工作的东西...

XmlSerializer 实际上可以在序列化时将可枚举数据传入和传出。它对实现 IEnumerable<T> 的 class 有特殊处理。来自 docs:

The XmlSerializer gives special treatment to classes that implement IEnumerable or ICollection. A class that implements IEnumerable must implement a public Add method that takes a single parameter. The Add method's parameter must be of the same type as is returned from the Current property on the value returned from GetEnumerator, or one of that type's bases.

当序列化这样的 classes 时,XmlSerializer 只是遍历枚举并将每个当前值写入输出流。它 不会 首先将整个可枚举项加载到列表中。因此,如果您有一些 Linq 查询以块的形式从数据库中动态分页 T 类型的结果(例如 here),您可以将它们全部序列化而无需使用以下命令一次加载它们包装纸:

// Proxy class for any enumerable with the requisite `Add` methods.
public class EnumerableProxy<T> : IEnumerable<T>
{
    [XmlIgnore]
    public IEnumerable<T> BaseEnumerable { get; set; }

    public void Add(T obj)
    {
        throw new NotImplementedException();
    }

    #region IEnumerable<T> Members

    public IEnumerator<T> GetEnumerator()
    {
        if (BaseEnumerable == null)
            return Enumerable.Empty<T>().GetEnumerator();
        return BaseEnumerable.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion
}

注意这个class只对序列化有用,对反序列化没有用。这是一个如何使用它的例子:

public class RootObject<T>
{
    [XmlIgnore]
    public IEnumerable<T> Results { get; set; }

    [XmlArray("Results")]
    public EnumerableProxy<T> ResultsProxy { 
        get
        {
            return new EnumerableProxy<T> { BaseEnumerable = Results };
        }
        set
        {
            throw new NotImplementedException();
        }
    }
}

public class TestClass
{
    XmlWriter xmlWriter;
    TextWriter textWriter;

    public void Test()
    {
        try
        {
            var root = new RootObject<int>();
            root.Results = GetResults();

            using (textWriter = new StringWriter())
            {
                var settings = new XmlWriterSettings { Indent = true, IndentChars = "  " };
                using (xmlWriter = XmlWriter.Create(textWriter, settings))
                {
                    (new XmlSerializer(root.GetType())).Serialize(xmlWriter, root);
                }
                var xml = textWriter.ToString();
                Debug.WriteLine(xml);
            }
        }
        finally
        {
            xmlWriter = null;
            textWriter = null;
        }
    }

    IEnumerable<int> GetResults()
    {
        foreach (var i in Enumerable.Range(0, 1000))
        {
            if (i > 0 && (i % 500) == 0)
            {
                HalfwayPoint();
            }
            yield return i;
        }
    }

    private void HalfwayPoint()
    {
        if (xmlWriter != null)
        {
            xmlWriter.Flush();
            var xml = textWriter.ToString();
            Debug.WriteLine(xml);
        }
    }
}

如果您在 HalfwayPoint() 中设置中断,您会看到 XML 的一半已经写完,同时仍在遍历可枚举。 (当然,我只是出于测试目的写入字符串,而您可能正在写入文件。)