C#上位机开发数据持久化:excel报表导入导出

C# 上位机 Excel 报表导入导出(工控场景最佳实践)

核心用 2 个库:NPOI(无 Office 依赖,工控首选)、EPPlus(仅支持 xlsx,需授权),优先选 NPOI 适配现场无 Office 环境。✅全是可直接复用代码 + 工控适配要点,贴合 WinForm/WPF 场景

✨一、先装 NuGet 包(必看)

  • NPOI(推荐):Install-Package NPOI
  • EPPlus(备选):Install-Package EPPlus

✨二、核心导出(工控常用 2 种场景)

场景 1:实时采集数据导出(比如 PLC 采集的产线数据)

cs 复制代码
// 通用导出方法(直接复制用)
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;

public void ExportPLCDataToExcel(List<PLCData> dataList, string savePath)
{
    // 1.创建工作簿(xlsx用XSSFWorkbook,xls用HSSFWorkbook)
    IWorkbook workbook = Path.GetExtension(savePath).ToLower() == ".xlsx" 
        ? new XSSFWorkbook() : new HSSFWorkbook();
    ISheet sheet = workbook.CreateSheet("产线采集数据");

    // 2.写表头(工控报表固定表头,适配产线、时间、数值、状态)
    IRow headerRow = sheet.CreateRow(0);
    headerRow.CreateCell(0).SetCellValue("采集时间");
    headerRow.CreateCell(1).SetCellValue("产线编号");
    headerRow.CreateCell(2).SetCellValue("温度(℃)");
    headerRow.CreateCell(3).SetCellValue("压力(MPa)");
    headerRow.CreateCell(4).SetCellValue("运行状态");

    // 3.填数据(适配工控数据类型,避免空值报错)
    for (int i = 0; i < dataList.Count; i++)
    {
        IRow row = sheet.CreateRow(i + 1);
        row.CreateCell(0).SetCellValue(dataList[i].CollectTime.ToString("yyyy-MM-dd HH:mm:ss"));
        row.CreateCell(1).SetCellValue(dataList[i].LineNo);
        row.CreateCell(2).SetCellValue(dataList[i].Temp);
        row.CreateCell(3).SetCellValue(dataList[i].Pressure);
        row.CreateCell(4).SetCellValue(dataList[i].IsRunning ? "运行中" : "停机");
    }

    // 4.自适应列宽(工控报表必备)
    for (int i = 0; i < 5; i++) sheet.AutoSizeColumn(i);

    // 5.保存文件(上位机弹窗选路径用SaveFileDialog)
    using (FileStream fs = new FileStream(savePath, FileMode.Create, FileAccess.Write))
    {
        workbook.Write(fs);
    }
}

// 工控数据模型(对应PLC采集)
public class PLCData
{
    public DateTime CollectTime { get; set; } //采集时间
    public string LineNo { get; set; }       //产线号
    public decimal Temp { get; set; }        //温度
    public decimal Pressure { get; set; }    //压力
    public bool IsRunning { get; set; }      //运行状态
}

场景 2:导出模板(让现场人员填写参数再导入)

cs 复制代码
// 导出参数模板(比如配方参数、设备参数)
public void ExportParamTemplate(string savePath)
{
    IWorkbook workbook = new XSSFWorkbook();
    ISheet sheet = workbook.CreateSheet("设备参数模板");
    IRow header = sheet.CreateRow(0);
    
    // 模板表头+备注,引导现场填写
    header.CreateCell(0).SetCellValue("设备编号(必填)");
    header.CreateCell(1).SetCellValue("设定转速(rpm)");
    header.CreateCell(2).SetCellValue("设定阈值");
    header.CreateCell(3).SetCellValue("备注");

    // 锁定表头,仅允许编辑数据行(工控模板防误改)
    sheet.CreateRow(1); //留一行示例行
    using (FileStream fs = new FileStream(savePath, FileMode.Create))
    {
        workbook.Write(fs);
    }
}

✨三、核心导入(工控 2 种常用场景)

场景 1:导入参数(模板导入,校验必填项)

cs 复制代码
// 导入设备参数,带校验(工控必加,防止无效数据)
public List<DeviceParam> ImportDeviceParam(string filePath)
{
    List<DeviceParam> paramList = new List<DeviceParam>();
    using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
    {
        IWorkbook workbook = Path.GetExtension(filePath).ToLower() == ".xlsx"
            ? new XSSFWorkbook(fs) : new HSSFWorkbook(fs);
        ISheet sheet = workbook.GetSheetAt(0);

        // 从第2行开始读(跳过表头)
        for (int i = 1; i <= sheet.LastRowNum; i++)
        {
            IRow row = sheet.GetRow(i);
            if (row == null) continue;

            // 工控关键:必填项校验(设备编号不能为空)
            if (row.GetCell(0) == null || string.IsNullOrEmpty(row.GetCell(0).ToString().Trim()))
            {
                throw new Exception($"第{i+1}行:设备编号不能为空!");
            }

            DeviceParam param = new DeviceParam();
            param.DeviceNo = row.GetCell(0).ToString().Trim();
            param.SetSpeed = row.GetCell(1)?.NumericCellValue ?? 0; //空值赋默认
            param.SetThreshold = row.GetCell(2)?.NumericCellValue ?? 0;
            param.Remarks = row.GetCell(3)?.ToString() ?? "";
            
            paramList.Add(param);
        }
    }
    return paramList;
}

// 设备参数模型
public class DeviceParam
{
    public string DeviceNo { get; set; }
    public double SetSpeed { get; set; }
    public double SetThreshold { get; set; }
    public string Remarks { get; set; }
}

场景 2:导入批量数据(比如批量导入生产计划)

cs 复制代码
// 批量导入,兼容空行、格式错误
public List<ProducePlan> ImportProducePlan(string filePath)
{
    List<ProducePlan> plans = new List<ProducePlan>();
    using (FileStream fs = new FileStream(filePath, FileMode.Open))
    {
        IWorkbook workbook = new XSSFWorkbook(fs);
        ISheet sheet = workbook.GetSheetAt(0);

        for (int i = 1; i <= sheet.LastRowNum; i++)
        {
            IRow row = sheet.GetRow(i);
            if (row == null || row.GetCell(0) == null) continue;

            // 格式转换容错(工控数据常出现格式错误)
            var plan = new ProducePlan
            {
                PlanNo = row.GetCell(0).ToString().Trim(),
                ProductName = row.GetCell(1)?.ToString() ?? "",
                // 日期格式容错
                StartDate = DateTime.TryParse(row.GetCell(2)?.ToString(), out var dt) ? dt : DateTime.Now,
                TargetQty = row.GetCell(3)?.NumericCellValue ?? 0
            };
            plans.Add(plan);
        }
    }
    return plans;
}

✨四、工控场景关键优化(必加)

  1. 无 Office 依赖:必须用 NPOI,现场工控机大概率没装 Office,EPPlus 仅 xlsx 且商用需授权

  2. 大文件处理:导入导出超过 1 万行,用分批读写,避免内存溢出

  3. 数据校验:必填项、数据范围(比如转速不能为负)、格式校验,防止写入 PLC 出错

  4. 路径选择:上位机用SaveFileDialog(导出)和OpenFileDialog(导入),贴合 WinForm/WPF

    cs 复制代码
    // 快速调用弹窗选路径
    public string GetSaveExcelPath()
    {
        SaveFileDialog sfd = new SaveFileDialog();
        sfd.Filter = "Excel文件|*.xlsx;*.xls";
        return sfd.ShowDialog() == DialogResult.OK ? sfd.FileName : "";
    }
  5. 异常捕获:包裹 try-catch,提示具体行号,方便现场排查

✨五、快速调用示例(WinForm/WPF 直接用)

cs 复制代码
// 导出按钮点击事件
private void btnExport_Click(object sender, EventArgs e)
{
    try
    {
        string path = GetSaveExcelPath();
        if (string.IsNullOrEmpty(path)) return;
        // plcDataList是你从PLC采集到的数据
        ExportPLCDataToExcel(plcDataList, path);
        MessageBox.Show("导出成功!");
    }
    catch (Exception ex)
    {
        MessageBox.Show("导出失败:" + ex.Message);
    }
}

// 导入按钮点击事件
private void btnImport_Click(object sender, EventArgs e)
{
    try
    {
        OpenFileDialog ofd = new OpenFileDialog();
        ofd.Filter = "Excel文件|*.xlsx;*.xls";
        if (ofd.ShowDialog() != DialogResult.OK) return;
        
        var paramList = ImportDeviceParam(ofd.FileName);
        // 导入后可直接写入PLC或保存数据库
        MessageBox.Show($"导入成功,共{paramList.Count}条参数!");
    }
    catch (Exception ex)
    {
        MessageBox.Show("导入失败:" + ex.Message);
    }
}
相关推荐
派大鑫wink2 小时前
Stream 流:简化集合操作的利器
java·开发语言
小小8程序员2 小时前
除了 gcc/g++,还有哪些常用的 C/C++ 编译器?
c语言·开发语言·c++
亓才孓2 小时前
java中的Math.Radom拓展
开发语言·python·算法
lkbhua莱克瓦242 小时前
基础-SQL-DQL
java·开发语言·数据库·笔记·mysql·dql
laocooon5238578862 小时前
Rust 编程语言教学目录
开发语言·后端·rust
lkbhua莱克瓦242 小时前
基础-SQL-DCL
开发语言·数据库·笔记·mysql·dcl
小希smallxi2 小时前
Rust语言入门
开发语言·后端·rust
悟能不能悟2 小时前
springboot controller返回的是HttpServletResponse成功返回excel文件流,失败就返回失败参数
spring boot·后端·excel
悟能不能悟3 小时前
JAVA 对象转为二级制流,再转化为base64
java·开发语言