
在许多基于 .NET 的业务系统中,结构化数据通常以 DataTable 的形式存在。当这些数据需要被分发、归档,或以只读报表的形式对外提供时,使用 C# 将 DataTable 导出为 PDF 就成为一个非常常见且实用的需求。
与 Excel、CSV 等格式相比,PDF 更适合那些更关注版式稳定性、视觉一致性和文档完整性的场景,而不是数据的可编辑性。因此,PDF 特别适用于报表、发票、审计记录以及系统自动生成的正式文档。
本文采用以代码为核心的方式,讲解如何在 C# 中将 DataTable 转换为 PDF,重点放在具体实现细节上。示例基于 Spire.PDF for .NET,并使用其中的 PdfGrid 组件,将 DataTable 的内容以结构化表格的形式渲染到 PDF 文档中。
目录
从本质上看,将 DataTable 导出为 PDF 是一个数据绑定 + 渲染的问题,而不是一个底层绘图问题。
也就是说,你并不需要手动计算每一行的位置、列宽,或自己处理分页逻辑。更合理的做法是:将现有的 DataTable 直接绑定到一个 PDF 表格组件上,由渲染引擎自动完成布局和分页。
在 Spire.PDF for .NET 中,这个角色正是由 PdfGrid 类来承担的。
PdfGrid 是 Spire.PDF for .NET 中的一个类,用于在 PDF 文档中显示结构化表格数据。它将“行、列、表头、分页”等概念作为核心概念来处理,而不仅仅是进行图形绘制操作。
从技术角度来看,PdfGrid 提供了以下关键能力:
因此,将 DataTable 导出为 PDF 更像是一种声明式操作:你只需要说明“要渲染哪些数据”,而具体“如何在页面中排版和分页”,则交由 PDF 引擎自动完成。
接下来的章节将围绕这一思路,重点讲解具体实现方式以及在实际项目中常见的优化细节。
本文中的所有示例代码同时适用于 .NET Framework 和现代 .NET(6 及以上) 项目。 整个实现完全基于托管代码,不需要任何平台相关的额外配置。
可以通过 NuGet 安装 Spire.PDF for .NET:
Install-Package Spire.PDF
你也可以直接 下载 Spire.PDF for .NET,并手动将其添加到项目中。
安装完成后,该库将提供完整的 PDF 文档创建、页面管理、表格渲染以及样式控制 API。
在环境准备完成后,将 DataTable 导出为 PDF 就变成了一个线性、以实现为导向的过程。
核心思想是:将现有的 DataTable 绑定到 PdfGrid,并将布局、分页和表格绘制的职责全部交给 PDF 引擎处理,而不是手动绘制行、列或边框。
从实现角度来看,整个流程通常包含以下几个步骤:
注意:如果 DataTable 中包含中文字符,默认字体可能无法渲染,导致 PDF 显示空白。因此在渲染前应使用支持中文的基础字体(如“微软雅黑”或“宋体”),并通过 PdfTrueTypeFont 应用到表格样式中。
在真实项目中,这些步骤通常会在同一段代码流程中顺序执行。下面的示例代码展示了一个完整、可直接使用的实现。
以下示例使用了一个偏业务报表风格的 DataTable 结构,以贴近实际应用场景。需要注意的是,DataTable 的数据来源(数据库、API 或内存计算)并不会影响导出逻辑本身。
using Spire.Pdf;
using Spire.Pdf.Graphics;
using Spire.Pdf.Grid;
using System.Data;
using System.Drawing;
DataTable dataTable = new DataTable();
dataTable.Columns.Add("OrderId", typeof(int));
dataTable.Columns.Add("CustomerName", typeof(string));
dataTable.Columns.Add("OrderDate", typeof(DateTime));
dataTable.Columns.Add("TotalAmount", typeof(decimal));
// 使用国内常见企业或品牌,金额也贴近实际业务
dataTable.Rows.Add(1001, "京东商城", DateTime.Today, 1580.75m);
dataTable.Rows.Add(1002, "天猫超市", DateTime.Today, 890.50m);
dataTable.Rows.Add(1003, "苏宁易购", DateTime.Today, 2340.00m);
dataTable.Rows.Add(1004, "小米官方商城", DateTime.Today, 1760.20m);
dataTable.Rows.Add(1005, "拼多多", DateTime.Today, 1120.00m);
dataTable.Rows.Add(1006, "国美在线", DateTime.Today, 980.30m);
PdfDocument document = new PdfDocument();
PdfPageBase page = document.Pages.Add();
PdfGrid grid = new PdfGrid();
grid.DataSource = dataTable;
// 应用包含中文的字体以防止文字丢失
PdfGridCellStyle cellStyle = new PdfGridCellStyle();
cellStyle.Font = new PdfTrueTypeFont(new Font("微软雅黑", 9f), true);
grid.Rows.ApplyStyle(cellStyle);
grid.Draw(page, new PointF(0, 40f));
document.SaveToFile("DataTableToPDF.pdf");
document.Close();
这段代码已经完成了 DataTable → PDF 的完整导出流程。下面是生成的 PDF 文件效果预览:

该实现的关键技术特点包括:
最终生成的 PDF 是一个结构清晰、支持分页的表格文档,能够准确反映 DataTable 的结构和数据内容。 在多数场景下,这种方式已经是一个可直接用于生产环境的解决方案。
当然,在实际项目中,通常还会对表格的位置、页面大小、方向以及样式提出更高要求。 接下来的章节将重点介绍如何在不改变核心导出逻辑的前提下,对布局、分页和视觉效果进行精细控制。
在真实的业务文档中,表格通常只是页面内容的一部分。页面尺寸、表格起始位置以及分页方式,共同决定了表格数据在一页或多页中的呈现效果。
在 PdfGrid 中,这些问题并不是由表格本身单独管理的,而是在创建页面时以及调用 Draw 方法进行渲染时解决。PdfGrid 不直接负责页面切换或绝对定位,真正起作用的是页面配置以及绘制时传入的参数。
下面的示例展示了在实际报表中非常常见的一种布局与分页配置方式。
PdfDocument document = new PdfDocument();
// 创建 A4 页面并设置页边距
PdfPageBase page = document.Pages.Add(
PdfPageSize.A4,
new PdfMargins(40),
PdfPageRotateAngle.RotateAngle0, // 页面坐标系旋转角度
PdfPageOrientation.Landscape // 页面方向:横向
);
PdfGrid grid = new PdfGrid();
grid.DataSource = dataTable;
// 启用跨页重复表头
grid.RepeatHeader = true;
// 定义表格起始绘制位置
float startX = 40f;
float startY = 80f;
// 应用包含中文的字体以防止文字丢失
PdfGridCellStyle cellStyle = new PdfGridCellStyle();
cellStyle.Font = new PdfTrueTypeFont(new Font("微软雅黑", 9f), true);
grid.Rows.ApplyStyle(cellStyle);
// 绘制表格
grid.Draw(page, new PointF(startX, startY));
应用页面配置后的 PDF 效果如下:

上述渲染行为可以理解为在绘制阶段依次应用的一系列布局与页面流规则:
PdfPageBase
RepeatHeader
Draw 方法
通过对页面几何属性、表格起始位置和分页行为的统一配置,PdfGrid 能够在无需手动控制分页或逐行布局的情况下,稳定地生成多页表格文档。
在实际的 PDF 报表中,页码通常也是必不可少的组成部分。如果你需要为 PDF 添加页码,可以参考:如何使用 C# 为 PDF 添加页码。
在布局稳定之后,下一步通常就是对表格外观进行控制。PdfGrid 提供了一套集中式的样式模型,可以在不影响数据绑定和分页逻辑的前提下,对整个表格、列或行进行样式定制。
下面的示例整合了在报表类场景中最常见的一些样式设置方式。
PdfDocument document = new PdfDocument();
PdfPageBase page = document.AppendPage();
PdfGrid grid = new PdfGrid();
grid.DataSource = dataTable;
// 创建并应用表头样式
PdfGridCellStyle headerStyle = new PdfGridCellStyle();
headerStyle.Font =
new PdfFont(PdfFontFamily.Helvetica, 10f, PdfFontStyle.Bold);
headerStyle.BackgroundBrush =
new PdfSolidBrush(Color.FromArgb(60, 120, 200));
headerStyle.TextBrush = PdfBrushes.White;
grid.Headers.ApplyStyle(headerStyle);
// 创建行样式
PdfGridCellStyle defaultStyle = new PdfGridCellStyle();
defaultStyle.Font = new PdfTrueTypeFont(new Font("微软雅黑", 9f), true);
PdfGridCellStyle alternateStyle = new PdfGridCellStyle();
alternateStyle.BackgroundBrush =
new PdfSolidBrush(Color.LightSkyBlue);
alternateStyle.Font = new PdfTrueTypeFont(new Font("微软雅黑", 10f), true);
// 应用行样式(隔行变色)
for (int rowIndex = 0; rowIndex < grid.Rows.Count; rowIndex++)
{
if (rowIndex % 2 == 0)
{
grid.Rows[rowIndex].ApplyStyle(defaultStyle);
}
else
{
grid.Rows[rowIndex].ApplyStyle(alternateStyle);
}
}
// 明确设置列宽
grid.Columns[0].Width = 60f; // OrderId
grid.Columns[1].Width = 140f; // CustomerName
grid.Columns[2].Width = 90f; // OrderDate
grid.Columns[3].Width = 90f; // TotalAmount
// 绘制表格
grid.Draw(page, new PointF(40f, 80f));
应用上述样式后的 PDF 效果如下:

表头样式
数据行样式
列宽控制
注意:样式必须在调用 grid.Draw(...) 之前设置完成。
所有样式(表头、行、列)都会在渲染前统一解析,并在绘制过程中生效,不会影响分页或数据绑定逻辑。
如果你需要更复杂的表格样式控制(例如边框、对齐方式或条件样式),可以参考:使用 C# 创建并美化 PDF 表格。
当表格完成渲染后,最后一步就是导出 PDF 结果。无论输出到文件还是写入流中,表格的生成与渲染逻辑都是完全一致的,区别只在于输出目标。
将 PDF 直接保存为文件,适合桌面应用、后台任务以及批量导出等场景。
document.SaveToFile("DataTableReport.pdf");
document.Close();
这种方式通常用于:
在 Web 系统中,通常不希望或不需要将 PDF 写入磁盘。 此时,可以直接将 PDF 内容写入内存流,再作为响应返回给客户端。
using (MemoryStream stream = new MemoryStream())
{
document.SaveToStream(stream);
document.Close();
byte[] pdfBytes = stream.ToArray();
// 将 pdfBytes 作为 HTTP 响应返回
}
这种方式可以很好地与 ASP.NET 控制器或 Minimal API 集成,无需创建任何临时文件。
如果你需要一个完整的 Web 示例,可以参考: 在 ASP.NET 中创建并返回 PDF 文档。
本节总结了在实际项目中,将 DataTable 导出为 PDF 时最容易遇到的一些问题及处理建议。
PdfGrid 会直接使用数据的字符串表示形式进行渲染。 因此,如果对格式有要求,应该在绑定之前对数据进行统一处理。
常见场景包括:
这些工作应当放在数据层完成,而不是在渲染阶段临时处理。
如果 DataTable 中包含 DBNull.Value,可能会导致单元格内容为空或布局不一致。
在绑定之前对数据进行规范化,可以避免这些问题:
row["TotalAmount"] =
row["TotalAmount"] == DBNull.Value ? 0m : row["TotalAmount"];
这种做法可以让渲染逻辑保持简单、可预期。
当 DataTable 的列较多或列内容较宽时,如果不做控制,表格很容易超出页面宽度。
常见的解决方式包括:
这些调整应当在布局层完成,而不是通过修改原始数据来规避问题。
当 DataTable 包含数百甚至上千行数据时,性能差异会变得更加明显。
实践中的一些建议包括:
例如,在大数据量场景下,在循环中频繁调用 grid.Rows[rowIndex].ApplyStyle(...) 可能会带来不必要的性能开销。如果不需要对每一行进行差异化处理,更推荐使用 grid.Rows.ApplyStyle(...) 这样的方式一次性应用统一样式。
此外,在 Web 环境中,PDF 生成过程最好放在请求线程之外执行,以避免阻塞请求处理。
在 C# 中,将 DataTable 导出为 PDF 并不需要手动构建表格或进行底层绘图操作。 通过 PdfGrid 直接绑定现有的 DataTable,就可以生成支持自动分页的 PDF 表格,同时对布局和外观保持良好的控制能力。
本文从实际开发角度出发,重点介绍了导出流程、布局定位、样式设置以及数据准备等关键问题。这套模式既适用于简单报表,也能够平滑扩展到多页、大数据量的复杂文档场景。
如果你计划在真实项目中评估或使用该方案,可以通过 E-ICEBLUE 申请 临时许可证,以不受限制地测试完整功能。
当你需要生成结构清晰、支持分页且版式稳定的表格型 PDF 文档时,PdfGrid 是非常合适的选择。 它能够自动处理列生成、表头和分页问题,相比手动绘制,更适合报表、发票和审计类文档。
数据规范化(如日期格式、数值精度、空值处理)应在绑定之前完成。 PdfGrid 更适合负责布局和视觉样式,而不是数据转换。
可以。PdfGrid 支持自动分页和表头重复显示。在大数据量场景下,避免逐行或逐单元格设置样式,而是使用表级或列级样式,有助于保持良好的性能表现。
Spire.Presentation for Java 11.1.3 已正式发布。本版本支持在将 PowerPoint 文件转换为其他格式时,解析 WEBP 格式图片,同时修复了两个已知问题。具体更新内容如下。
新功能:
问题修复:
https://www.e-iceblue.cn/Downloads/Spire-Presentation-JAVA.html
Spire.XLS 16.1.4 现已发布。该版本新增了一些功能,如支持转换指定单元格范围到 HTML,以及转换 Excel 到 PDF/UA 标准文件。此外,还修复了一个删除文档的 OleObject 不成功的问题。详情如下
新功能:
// 使用BYROW计算每行的平均分
sheet.Range["G2"].Formula = "=BYROW(B2:F2, LAMBDA(row, AVERAGE(row)))";
// 使用BYCOL计算每列的平均值
sheet.Range["B8"].Formula = "=BYCOL(B2:B7, LAMBDA(col, AVERAGE(col)))";
Workbook workbook = new Workbook();
workbook.LoadFromFile(inputFile);
Worksheet sheet = workbook.Worksheets[0];
CellRange cell = sheet.Range["A1:B3"];
string html = cell.HtmlString;
File.WriteAllText(outputFile, html);
CopyRangeOptions options = CopyRangeOptions.Transpose | CopyRangeOptions.All;
sheet["A1:C4"].Copy(sheet["D2:G3"], options);
sheet["A1:B5"].Copy(sheet["D5"], options);
workbook.SaveToFile(outputFile);
Workbook workbook = new Workbook();
workbook.LoadFromFile(inputFile);
workbook.ConverterSetting.PdfConformanceLevel = Spire.Xls.Pdf.PdfConformanceLevel.Pdf_UA1;
workbook.SaveToFile(outputFile, FileFormat.PDF);
workbook.Dispose();
问题修复:
Spire.PDF 12.1.6 现已正式发布。该版本支持获取和设置 PDF 2.0 版本。同时,一些在转换 PDF 到图片或 PDF/A,替换文本,加载 XPS 文件以及提取文本时出现的问题也得以成功被修复。更多详情如下。
新功能:
// 获取
PdfFileInfo info = doc.FileInfo;
PdfVersion version = info.Version;
//设置
doc.FileInfo.Version = PdfVersion.Version2_0;
问题修复:
Spire.Presentation 11.1.1 现已正式发布。该版本新增了基于正则表达式匹配并高亮文本的功能。同时,修复了多个 PPTX 转 PDF 过程中出现的问题,包括部分内容丢失以及默认字体配置不生效的问题。详情如下。
新功能:
Regex regex = new Regex(@"\bhello\b");
IAutoShape shape = (IAutoShape)ppt.Slides[0].Shapes[0];
TextHighLightingOptions options = new TextHighLightingOptions();
shape.TextFrame.HighLightRegex(regex, Color.Red, options);
问题修复:
https://www.e-iceblue.cn/Downloads/Spire-Presentation-NET.html
Spire.Doc for .NET 14.1.12 现已发布。本次更新引入了多项实用的新功能,包括:支持删除 Word 文档中的指定页面和空白页面、创建和操作 VBA 宏,以及从文档中获取完整的修订(Revision)信息。同时,我们还修复了多个已知问题,进一步提升了整体稳定性和性能。以下为本次版本更新的详细内容。
新功能:
HtmlExportOptions options = doc.HtmlExportOptions;
options.OfficeMathOutputMode = HtmlOfficeMathOutputMode.MathML;
doc.RemoveBlankPages(); //删除空白页
doc.RemovePages(new List {0,1,3});//删除指定页
Document doc = new Document();
doc.AddSection().AddParagraph().AppendText("wertyuiop[]fghjk");
//添加VBA项目到文档
VbaProject vbaProject = new VbaProject();
vbaProject.Name = "SampleVBAMacro";
doc.VbaProject = vbaProject;
//将模块添加到VBA项目
//模块1
VbaModule vbaModule1 = doc.VbaProject.Modules.Add("SampleModule1", VbaModuleType.StdModule);
vbaModule1.SourceCode = @"
Sub DocumnetInfo()
MsgBox ""create time: "" &Now()
MsgBox ""Pages:"" & ActiveDocument.Range.ComputeStatistics(wdStatisticPages)
End Sub
Sub WriteHello()
Selection.TypeText Text:=""Hello World!""
End Sub";
//模块2
VbaModule vbaModule2 = doc.VbaProject.Modules.Add("SampleModule2", VbaModuleType.StdModule);
vbaModule2.SourceCode = @"
Sub InsertCurrentDate()
Selection.TypeText Text:=Format(Now(),""yyyy-mm-dd hh:mm:ss"")
End Sub
Sub IndentParagraph()
Selection.ParagraphFormat.LeftIndent = InchesToPoints(0.5)
End Sub";
doc.SaveToFile("result.docm", FileFormat.Docm);
doc.Close();
Document doc = new Document();
doc.LoadFromFile("input.docx");
StringBuilder sb = new StringBuilder();
RevisionInfoCollection revisionInfoCollection = doc.GetRevisionInfos();
foreach (RevisionInfo revisionInfo in revisionInfoCollection)
{
sb.AppendLine("[author]:" + revisionInfo.Author + "\r\n" + " [RevisionType]:" + revisionInfo.RevisionType + "\r\n" + " [DateTime]:" + revisionInfo.DateTime.ToString() + "\r\n" + " [OwnerObject]:" + revisionInfo.OwnerObject + "\r\n" + " [OwnerObject.Owner]:" + revisionInfo.OwnerObject.Owner + "\r\n");
if (revisionInfo.OwnerObject is TextRange textRange)
{
TextRange range = (TextRange)textRange;
sb.AppendLine($"作用对象:文本范围 - 内容:{range.Text}");
}
}
File.WriteAllText(outputFile, sb.ToString());
doc.Dispose();
问题修复: