使用C# XmlSerializer对大量对象进行分块写入,以避免内存不足问题。

3
我喜欢XmlSerialize的工作方式,简单而优雅,还可以使用属性。但是,在将所有对象构建到序列化为xml文件之前,我遇到了内存不足的问题。
我正在从SQL数据库中填充一个对象,并打算使用XmlSerialize将该对象写入XML。它适用于小型子集,但如果我尝试从数据库中获取所有对象,则会出现内存不足异常。
是否有一些XmlSerialize的功能可以允许我从数据库中获取100个对象的批次,然后写入它们,获取下一个100个对象的批次并附加到xml中?
我希望不必转换为XmlDocument或其他需要更多手动编码工作的东西...

1
@GauravSharma 是的,同意。但我不确定在C#中XmlSerializer是否支持对现有文件进行“追加”操作? - Kairan
嗨,Kairan,我不确定XmlSerializer的附加功能。即使这样做,对于非常大的文件,它也容易出现内存溢出异常。您应该考虑找出不超时的文件的最大大小,然后开始创建新文件。类似于日志框架Log4Net的操作方式。 - Gaurav Sharma
这可能与您的情况无关,但我记得在3-4年前尝试使用内置的.Net XML序列化程序时遇到了几个严重的问题。最终我放弃了它。现在有一些可替代的序列化程序包可供选择,也许您应该考虑使用它们。 - RenniePet
你有能力编写一个从数据库中分块获取数据的 IEnumerable<T> 吗?我认为你有这个能力。 - dbc
@dbc 我需要稍后将其读回来,以便将数据放回数据库中... 将数据库导入/导出为xml格式。 - Kairan
显示剩余4条评论
1个回答

5

XmlSerializer 在序列化时实际上可以在枚举数据内部进行流式传输。它对于实现 IEnumerable<T> 接口的类有特殊处理。根据文档所述:

XmlSerializer 对实现 IEnumerable 或 ICollection 的类进行特殊处理。实现 IEnumerable 接口的类必须实现一个公共的 Add 方法,该方法需要一个参数。Add 方法的参数类型必须与从 GetEnumerator 返回的值的 Current 属性返回的类型相同,或者是该类型的基类之一。

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

// 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
}

请注意,这个类只适用于序列化,不适用于反序列化。以下是使用它的示例:

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(当然,我只是为测试目的将其写入字符串,而您可能会将其写入文件)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接