LOGO 首页 OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 技术文档 其他文档  
 
网站管理员

【C#.Net】不装 Office 也能操作 Excel:Wisdom.Excel 使用指南

admin
2026年11月11日 11:13 本文热度 529

写在前面

服务端生成 Excel 报表,大概是 .NET 项目里最常见的轮子需求之一。可选方案无非三类:Microsoft.Office.Interop(要求机器上装 Office,服务端基本不能用)、EPPlus/ClosedXML(功能强但许可证和体积让人犹豫)、MiniExcel(轻量但只做数据搬运)。

Wisdom.Excel 走的是第四条路:零第三方依赖,自己解析 OOXML。全文只有一个内部依赖 Wisdom.Office.Core(数据模型),不要求安装 Microsoft Office 或 WPS。NuGet 安装:

dotnet add package Wisdom.Excel

支持 net461 / netstandard2.0 / net7.0 / net8.0 / net9.0 / net10.0 六份目标框架,由同一份源码编译(源码里没有任何 #if NET* 条件编译),.NET Framework 4.6.1 以上的老项目和 Unity 都能用。

这篇覆盖:读取、生成、对象映射、百万行流式查询、模板填充,以及图表 / 条件格式这些"装饰型"能力。

一、数据模型:三个类走天下

所有操作都建立在 OfficeWorkbook → Worksheet → Cell 这条链上,模型定义在 Wisdom.Office.Core:

using Wisdom.Excel;
using Wisdom.Office.Core;

OfficeWorkbook workbook = XlsxReader.Load("sales.xlsx");   // 也支持 Stream / byte[]

foreach (Worksheet sheet in workbook.Worksheets)
{
    Console.WriteLine(sheet.Name);
    foreach (Cell cell in sheet.Cells)                     // 只遍历真实存在的单元格
    {
        if (cell.Value != null)
            Console.WriteLine($"{cell.Row}:{cell.Column} = {cell.DisplayText()}");
    }
    Console.WriteLine($"范围 {sheet.LastRow} x {sheet.LastColumn},合并 {sheet.MergedRanges.Count} 处");
}

Cell.Value 接受 string / double / bool / DateTime(decimal 也能写,落盘时转 double)。只想抽文本的话,一行就够:

string text = XlsxReader.ExtractText("sales.xlsx");

隐藏表 / 隐藏行列默认不参与读取,需要时显式打开:

OfficeWorkbook all = XlsxReader.Load("sales.xlsx", new ExcelReadOptions
{
    IncludeHiddenWorksheets = true,
    IncludeHiddenRows = true,
    IncludeHiddenColumns = true
});

读回来的东西比想象中多:值与缓存公式结果、字体 / 颜色 / 填充 / 对齐 / 边框 / 数字格式、合并区域、列宽行高、冻结窗格、打印区域与纸张方向,甚至图表、条件格式和锚定图片。这意味着「加载 → 改几个格子 → 保存」不会把原文件的装饰弄丢。

二、生成:样式、公式、打印设置

OfficeWorkbook workbook = new OfficeWorkbook();
Worksheet sheet = workbook.AddWorksheet("明细");

sheet.GetCell(1, 1).Value = "产品";
sheet.GetCell(1, 2).Value = "数量";
sheet.GetCell(1, 3).Value = "金额";

sheet.GetCell(2, 1).Value = "无线键盘";
sheet.GetCell(2, 2).Value = 12;

Cell amount = sheet.GetCell(2, 3);
amount.Value = 299.5;
amount.Style = new CellStyle
{
    NumberFormat = "#,##0.00",
    Bold = true,
    FillColor = OfficeColor.FromHtml("#F2F2F2"),
    HorizontalAlignment = HorizontalAlignment.Right
};

sheet.GetCell(3, 3).Formula = "SUM(C2:C2)";   // 公式文本
sheet.Merge("A4:C4");
sheet.FrozenRows = 1;                          // 冻结表头
sheet.SetColumnWidth(1, 24);
sheet.ShowGridLines = false;

sheet.SetPrintArea("A1:C20");
sheet.PrintSetup = PageSetup.A4();
sheet.PrintSetup.IsLandscape = true;
sheet.FitToPage = true;
sheet.CenterHorizontally = true;

XlsxWriter.Write(workbook, "sales.xlsx");      // 亦可写 Stream 或返回 byte[]

数字格式沿用 Excel 的分节语法 正数;负数;零;文本,DisplayText() 和导出 PDF / Word 时用的是同一套格式化实现——这一点在跨格式报表里很重要,不会出现"Excel 里是日期,PDF 里成了序列号"。

####(列宽不足时 Excel 的显示行为)在导出 PDF / Word 时可以选择是否模拟,由 Wisdom.Office 包的 WorkbookLayoutOptions.EmulateNumberOverflow 控制,而 DisplayText() 永远给完整文本。

三、对象集合直接收发

ExcelCollection 提供 MiniExcel 风格的映射,注解控制列名、顺序与数字格式:

public sealed class Order
{
    [ExcelColumn("订单号", Order = 1)] public string Code { get; set; }
    [ExcelColumn("数量", Order = 2)] public int Quantity { get; set; }
    [ExcelColumn("金额", Order = 3, Format = "#,##0.00")] public decimal Amount { get; set; }
    [ExcelColumn("下单时间", Order = 4, Format = "yyyy-mm-dd")] public DateTime Created { get; set; }
    [ExcelColumn(Ignore = true)] public string InternalNote { get; set; }
}

ExcelCollection.Write(workbook, orders, "订单");
List<Order> read = ExcelCollection.ToList<Order>(workbook.Worksheets[0]);
DataTable table = ExcelCollection.ToDataTable(workbook.Worksheets[0]);

读取时表头名不区分大小写,Excel 里缺的列保留属性默认值;日期没指定格式时自动补 yyyy-mm-dd hh:mm:ss。DataTable 与工作表可以双向。

四、百万行:ExcelQuery 流式读

XlsxReader.Load 会构建整个工作簿模型,遇到几十上百万行的文件就很吃力。ExcelQuery 绕过模型直接流式解析工作表部件,逐行返回:

foreach (ExcelRowData row in ExcelQuery.ReadRows("big.xlsx", "明细"))
{
    object name = row[1];          // 按列号取值,不存在返回 null
    if (row.Hidden) continue;
}

foreach (OrderRow item in ExcelQuery.Query<OrderRow>("big.xlsx", "明细"))
    Process(item);                 // 首行表头映射到对象,边读边反序列化

foreach (Dictionary<string, object> row in ExcelQuery.ReadDictionaries("big.xlsx", "明细"))
    Console.WriteLine(row["订单号"]);

语义与 MiniExcel 一致:共享字符串表仍会整体加载,枚举期间持有文件句柄,样式与日期系统按工作簿设置解释。目前未提供异步枚举(netstandard2.0 没有 IAsyncEnumerable)。

五、区域与行列编辑

ExcelRange 把 A1 区域当作一个块来操作:

ExcelRange header = sheet.Range("A1:C1");
header.Values = new object[,] { { "产品", "数量", "金额" } };
header.SetStyle(s => { s.Bold = true; s.FillColor = OfficeColor.LightGray; });

sheet.Range("A2:C2").CopyTo(sheet.Range("A3:C3"));
sheet.Range("A5:C5").Merge();
sheet.Range("A1:C20").AutoFitColumns(minWidth: 8, maxWidth: 40);   // 全角字符按 2 字符计

sheet.InsertRows(2, 1);
sheet.DeleteColumns(2, 1);

插入 / 删除行列时,单元格、行高列宽、隐藏标记、合并区域、自动筛选、数据验证和打印区域会同步平移——这是手工写偏移最容易漏的地方。Clear() 只清内容保留样式,ClearAll() 连同样式与合并一起清除。

六、表级功能

Worksheet copy = workbook.CopyWorksheet("明细", "明细副本");   // 深拷贝内容与样式
workbook.MoveWorksheet(0, 1);
workbook.RenameWorksheet(copy, "归档");                        // 校验 31 字符与非法字符
bool valid = OfficeWorkbook.IsValidWorksheetName("报表 2026");

sheet.TabColor = OfficeColor.FromHtml("#1F3A5F");              // 标签色
sheet.AutoFilter = new ExcelAutoFilter("A1:D100");
sheet.AutoFilter.ForColumn(0).Values.Add("华东");

sheet.GetCell(10, 1).Hyperlink = "https://example.com";        // 外链
sheet.GetCell(10, 2).Hyperlink = "#明细!A1";                   // 文档内跳转

sheet.DataValidations.Add(new ExcelDataValidation
{
    Range = "B2:B100",
    Type = DataValidationType.List,
    Formula1 = "待处理,处理中,已完成",
    ShowErrorMessage = true,
    ErrorMessage = "请从下拉列表中选择"
});

workbook.DefinedNames.Add(new ExcelDefinedName("Amounts", "明细!$C$2:$C$100"));

超链接只放行 http/https/mailto/ftp/ftps/file/tel/smb 与相对路径,javascript:、data: 这类危险协议在写出时直接丢弃——把它当作导出组件时,这是一条省心的人文安全兜底。筛选、数据验证、标签色、超链接与定义名都能被读回。

七、原生图表、条件格式与图片

ExcelChart chart = new ExcelChart
{
    Type = ExcelChartType.Column,      // 柱/条/折线/面积/饼/环形/堆积柱
    Title = "上半年销售额",
    Row = 4, Column = 1,
    WidthPoints = 360, HeightPoints = 200,
    ShowLegend = true, LegendPosition = "b",
    ValueAxisFormat = "#,##0"
};
ExcelChartSeries series = chart.AddSeries("看板!$A$2", "看板!$B$1:$G$1", "看板!$B$2:$G$2");
series.FillColor = OfficeColor.FromHtml("#00B050");
series.ShowDataLabels = true;
sheet.Charts.Add(chart);

ExcelConditionalFormat format = new ExcelConditionalFormat("B2:G2");
format.AddDataBar(new OfficeColor(0x63, 0xC3, 0x84));                  // 数据条
format.AddColorScale(new OfficeColor(0xF8, 0x69, 0x6A), new OfficeColor(0x63, 0xC3, 0x84));  // 色阶
format.AddIconSet("3TrafficLights1", new double[] { 33, 67 });         // 图标集
format.AddCellIs("greaterThan", "1000", new CellStyle { FontColor = OfficeColor.Red, Bold = true });
sheet.ConditionalFormats.Add(format);

sheet.Images.Add(new ExcelImage(OfficeImage.FromFile("logo.png"))
{
    Row = 2, Column = 9, WidthPoints = 24, HeightPoints = 24
});

图表数据源用区域引用,改动单元格后 Excel 里图表自动跟着变;不想依赖区域也可以直接喂字面量数组。条件格式的 dxf 样式是真实落盘的,不是截图糊上去的。

一点预期管理:图表按 OOXML 保存、由 Excel / WPS 渲染,本库不做位图渲染。所以导出 PDF / Word 时图表位置输出的是占位文本——要在 PDF 里看到图,请另存一份图片版并用 ExcelImage 锚定。

八、模板填充

业务方给一张排好版的 .xlsx 模板,程序只管往里灌数据,这是国内项目的高频需求。占位符 {{key}} / {{key:format}},循环 {{#each list}} … {{/each}}:

A6: {{#each items}}{{name}}    B6: {{qty}}    C6: {{price:0.00}}    D6: {{@number}}    E6: {{/each}}
TemplateContext context = new TemplateContext();
context["customerName"] = "示例客户";
context["items"] = new[]
{
    new { name = "无线键盘", qty = 2, price = 199.0 },
    new { name = "机械鼠标", qty = 5, price = 89.9 }
};

XlsxTemplate.Fill("template.xlsx", "filled.xlsx", context);

循环按行展开时保留模板行的样式、行高与合并;空列表会把整个循环块清掉,而不是留下一行孤零零的标记。也可在内存模型上填充(XlsxTemplate.Fill(workbook, context) 返回新工作簿,不动原模板)。

九、CSV 与旧版 .xls

ExcelCsv.Write(workbook, "export.csv");              // 默认 UTF-8 BOM、逗号
OfficeWorkbook csv = ExcelCsv.Read("import.csv");    // 数值 / 布尔自动识别
ExcelCsv.Write(sheet, "export-tab.csv", new ExcelCsvOptions { Separator = '\t', WriteBom = false });

CSV 覆盖 RFC 4180 的引号、逗号与字段内换行,并带行列数与单字段长度上限(防 zip 炸弹式的畸形输入)。

老古董文件也能读:

OfficeWorkbook legacy = XlsReader.Load("legacy.xls");   // BIFF8,Excel 97-2003

XlsReader 自己解析 OLE2 容器与 BIFF8 记录,值、样式、合并区域、窗格与元数据都能读;公式只保留缓存值,不支持 BIFF5/7 和加密工作簿,.xls 也不支持写回。

已知边界

  • 不支持写 .xls;.xlsx 里的宏(xlsm)不保留 VBA 内容。
  • 加密工作簿、外部工作簿链接、数据透视表、切片器、形状 / 批注不解析。
  • 没有公式计算引擎:公式文本可以写入,值需要调用方提供,或由 Excel 打开时重算。
  • 一个已知坑位:net461 上 ZipArchive 延迟解析中央目录,损坏的条目名会在完全无关的调用点抛 ArgumentException。本包统一走 PackageArchive.OpenForRead,立即物化目录并把这类异常收敛成 InvalidDataException,六个框架行为一致。

小结

Wisdom.Excel 的定位很清晰:把 XLSX 当作一个可以直接读写的 ZIP + XML 包来处理,覆盖到「企业报表」这个场景需要的绝大部分——样式、数字格式、公式、合并、冻结、打印、图表、条件格式、模板填充、流式读取、CSV、老 .xls,代价是明确放弃宏、透视表和公式计算这些重活。

如果你的项目只需要数据搬运,MiniExcel 更轻;如果你需要 PDF / Word / PPT 之间互转,去看同系列的 Wisdom.Office 门面包。只操作 Excel 且在意依赖纯净度的话,这个包值得一试。


阅读原文:点击这里


该文章在 2026/11/11 11:13:43 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-9  粤公网安备44030602007207号