DataSet 是 ADO.NET 中用来在内存里保存多张表及其关系的容器,很多业务场景需要把查询结果导出为 XML 文件,比如做数据交换、生成配置文件或临时备份。在 C# 中实现这个需求最简单也最推荐的方式就是调用 DataSet 自带的 WriteXml 方法,而不是手动拼接 XML 字符串。WriteXml 会根据 DataTable 的结构和行数据生成标准 XML 文档,还能按需包含架构信息,避免接收方不知道字段类型和约束。

接下来分别从文件保存、写入模式控制和字符流输出几个角度展开。每个角度都会给出可运行代码,并解释不同选项的适用场景。
一、使用 WriteXml 将 DataSet 保存到 XML 文件
WriteXml 最常用的重载是接受一个文件路径,直接把整个 DataSet 序列化到磁盘。假设我们有一个包含客户信息的 DataSet,里面有一张 Customers 表,包含 Id、Name 和 CreatedAt 三列。先创建表结构、添加几行数据,然后调用 WriteXml 保存。代码大致如下:
using System;
using System.Data;
class Program
{
static void Main()
{
DataSet ds = new DataSet("CustomerDataSet");
DataTable table = new DataTable("Customers");
table.Columns.Add("Id", typeof(int));
table.Columns.Add("Name", typeof(string));
table.Columns.Add("CreatedAt", typeof(DateTime));
table.Rows.Add(1, "张三", new DateTime(2024, 3, 1));
table.Rows.Add(2, "李四", new DateTime(2024, 3, 5));
ds.Tables.Add(table);
ds.WriteXml(@"C:\temp\customers.xml");
Console.WriteLine("已保存");
}
}
这段代码会把 DataSet 写到 C:\temp\customers.xml 文件中。默认情况下 WriteXml 只导出数据,不包含表结构。也就是说生成的文件中会有 <CustomerDataSet> 根节点,下面挂着多个 <Customers> 行节点,但不会包含列的类型、是否允许空等信息。对于只需要交换数据的场景这已经够用,但如果接收方需要根据 XML 重建表结构,就得额外指定模式。
还要注意路径中的反斜杠,C# 普通字符串里 @ 前缀可以避免转义,因此 @"C:\temp\customers.xml" 会按原样保留每一个反斜杠。如果不用 @,需要写成 "C:\\temp\\customers.xml"。文件所在目录必须存在,否则会抛出 DirectoryNotFoundException。
文件写入过程是同步的,如果数据量很大,UI 线程可能会卡顿,此时可以把 WriteXml 放到后台任务中执行。另外 WriteXml 会覆盖已存在的同名文件,如果不想覆盖,可以先判断 File.Exists 再决定是否备份。
二、用 XmlWriteMode 控制 XML 内容和结构
WriteXml 除了路径参数,还可以传入第二个参数 XmlWriteMode。该枚举有三个值:IgnoreSchema、WriteSchema 和 DiffGram。IgnoreSchema 是默认行为,只写数据;WriteSchema 会在 XML 中内联写入 XSD 架构,包含表名、列名、类型和约束;DiffGram 则是一种特殊格式,同时保存原始数据和修改后的数据,适用于跟踪数据变更和合并冲突。
下面的代码演示三种模式生成文件的差异。为了方便比较,使用同一个 DataSet 分别保存到三个文件:
using System;
using System.Data;
class Program
{
static void Main()
{
DataSet ds = CreateDataSet();
ds.WriteXml(@"C:\temp\data_only.xml", XmlWriteMode.IgnoreSchema);
ds.WriteXml(@"C:\temp\data_with_schema.xml", XmlWriteMode.WriteSchema);
ds.WriteXml(@"C:\temp\data_diffgram.xml", XmlWriteMode.DiffGram);
Console.WriteLine("三种模式已输出");
}
static DataSet CreateDataSet()
{
DataSet ds = new DataSet("OrderDataSet");
DataTable orders = new DataTable("Orders");
orders.Columns.Add("OrderId", typeof(int));
orders.Columns.Add("Product", typeof(string));
orders.Columns.Add("Quantity", typeof(int));
orders.Rows.Add(101, "键盘", 2);
orders.Rows.Add(102, "鼠标", 5);
ds.Tables.Add(orders);
return ds;
}
}
如果打开 data_only.xml,内容大致是 <OrderDataSet> 根节点下只有行数据;而 data_with_schema.xml 会先出现 <xs:schema> 节点,里面定义 <Orders> 表的列类型和主键等信息,随后才是数据部分。DiffGram 文件则包含 <diffgr:diffgram> 命名空间,用来区分原值和当前值。
选择哪种模式取决于用途:给第三方系统交换数据时,IgnoreSchema 更精简;需要让对方知道字段类型或后续用 ReadXml 恢复结构时,WriteSchema 更稳妥;如果要把 DataSet 当作变更跟踪容器,例如在多层架构中传递增删改结果,DiffGram 是适合的。注意 WriteSchema 会显著增加文件体积,因为每张表的每个字段都会有架构描述,数据量小的时候无所谓,大数据量下要权衡。
三、将 DataSet 保存为字符串或流,并处理编码问题
有些场景不一定要写到物理文件,例如需要在内存中生成 XML 字符串返回给前端,或者通过 HTTP 响应流输出。DataSet 还提供了 WriteXml 的重载,接受 Stream、TextWriter 和 XmlWriter 对象。如果只是想快速拿到 XML 字符串,也可以调用 GetXml 方法,但 GetXml 只能输出 IgnoreSchema 的数据,无法包含架构信息,而且字符编码由框架内部决定。
推荐的做法是使用 StringWriter 配合 WriteXml,并指定 XmlWriteMode,这样既能拿到字符串又能带上架构。示例:
using System;
using System.Data;
using System.IO;
using System.Text;
class Program
{
static void Main()
{
DataSet ds = new DataSet("ProductDataSet");
DataTable products = new DataTable("Products");
products.Columns.Add("Id", typeof(int));
products.Columns.Add("Name", typeof(string));
products.Columns.Add("Price", typeof(decimal));
products.Rows.Add(1, "显示器", 999.9m);
products.Rows.Add(2, "机械键盘", 399.0m);
ds.Tables.Add(products);
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
ds.WriteXml(sw, XmlWriteMode.WriteSchema);
}
string xml = sb.ToString();
Console.WriteLine(xml);
}
}
StringWriter 默认使用 UTF-16 编码,如果最终要写入文件或网络流,需要确认接收方支持的编码。若想生成 UTF-8 字节数组,可以使用 MemoryStream 和 StreamWriter,并指定 Encoding.UTF8。要注意 WriteXml 内部写 XML 声明时,编码信息来自 TextWriter 的 Encoding 属性。比如用 new StreamWriter(stream, Encoding.UTF8) 得到的 XML 声明会是 <?xml version="1.0" encoding="utf-8"?>。
另外如果保存到文件时遇到中文乱码,通常是因为文件编码和读取端不一致。WriteXml 重载中接受 Stream 时,可以自己创建 FileStream 和 StreamWriter,明确指定 UTF-8 或 GBK。例如 using (StreamWriter sw = new StreamWriter(@"C:\temp\data.xml", false, Encoding.UTF8)) { ds.WriteXml(sw); },这样能保证中文字符按 UTF-8 写入。不要依赖默认编码,不同环境下默认编码可能不同。
还有一个常见问题是 XML 中的特殊字符,比如小于号、大于号和与符号。WriteXml 会自动转义这些字符,因此如果数据列中包含类似英文的与符号,输出会自动变成合法的 XML 实体,从而保持 XML 合法。这也是不建议手动拼接 XML 的原因之一,因为手动拼接很容易漏掉转义。
四、用 XmlWriter 精细控制格式与命名空间
如果希望生成的 XML 有缩进、指定命名空间,或者在写入过程中进行流式处理,可以用 XmlWriter 配合 WriteXml。XmlWriter 可以通过 XmlWriterSettings 设置 Indent 为 true,让输出内容可读性更好。代码先创建 XmlWriterSettings,设置缩进和编码,然后创建 XmlWriter 指向文件,最后调用 ds.WriteXml(writer, XmlWriteMode.WriteSchema)。
using System;
using System.Data;
using System.Text;
using System.Xml;
class Program
{
static void Main()
{
DataSet ds = new DataSet("UserDataSet");
DataTable users = new DataTable("Users");
users.Columns.Add("UserId", typeof(int));
users.Columns.Add("UserName", typeof(string));
users.Rows.Add(1, "王五");
users.Rows.Add(2, "赵六");
ds.Tables.Add(users);
XmlWriterSettings settings = new XmlWriterSettings
{
Indent = true,
IndentChars = " ",
Encoding = Encoding.UTF8,
NewLineOnAttributes = false
};
using (XmlWriter writer = XmlWriter.Create(@"C:\temp\users_pretty.xml", settings))
{
ds.WriteXml(writer, XmlWriteMode.IgnoreSchema);
}
Console.WriteLine("带缩进的 XML 已保存");
}
}
通过 XmlWriterSettings 生成的 XML 会包含换行和缩进,每个元素层级用两个空格表示,阅读起来更清晰。如果不需要架构信息,可以传 IgnoreSchema;如果需要对方校验字段类型,则改成 WriteSchema。使用 XmlWriter 还可以在写入前添加命名空间声明,但 DataSet 的 WriteXml 本身不提供直接设置默认命名空间的重载,需要包装一个自定义 XmlWriter 或在写入后处理命名空间。不过对于大多数数据导出场景,默认命名空间并不影响使用。
保存完成后,读取时可以使用 DataSet.ReadXml 方法恢复。ReadXml 支持自动识别内联架构,如果 XML 是 WriteSchema 生成的,读取后 DataSet 会自动包含表结构;如果是 IgnoreSchema 生成的,则列类型会被推断为 string。另外如果 XML 是 DiffGram 格式,ReadXml 也能自动还原原始数据和修改后数据,通常配合 DataSet.Merge 使用。
在真实项目中,往往不只是把 DataSet 写成 XML,还需要处理并发、日志和异常。建议把 WriteXml 封装成通用方法,比如 SaveDataSetToXml(DataSet ds, string filePath, XmlWriteMode mode),内部做目录检查、文件备份和 try-catch。这样既避免重复代码,也能统一编码策略。总之,优先使用 WriteXml 而不是 GetXml 或手动拼接,可以最大程度减少编码乱码、特殊字符转义和架构丢失问题。
C# DataSetDataSet保存XMLWriteXml修改时间:2026-09-20 10:58:34