You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# Excel Interop无法保存且生成临时文件问题求助

Excel多工作表导出无规律失败问题排查与解决

问题背景

我开发的一款从Oracle数据源查询数据到DataTable,再导出至Excel的应用,正常运行4年,2022年12月起突然出现无规律的导出失败。Excel抛出“文档未保存”异常并生成tmp文件,仅在单次导出超100个工作表的场景中出现问题。

已排查内容

  • 已通过传递Excel应用实例确保未创建多实例,任务管理器中应用进程已成功释放;
  • 对已存在文件调用.Save时触发异常,同一导出流程中对新文件调用.SaveAs可正常执行;
  • 从数据库获取单个DataTable后,通过DataView筛选生成多个子表,若其中一个子表导出失败并生成tmp文件,其余同来源子表均无法导出且不生成tmp文件;
  • 故障出现时未修改代码,已用尽已知排查手段,需恢复多工作表导出至单个工作簿的功能。

导出相关代码

using System;
using System.IO;
using System.Collections.Generic;
using System.Data;
using System.Data.OleDb;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.CSharp;
using Outlook = Microsoft.Office.Interop.Outlook;
using MSWord = Microsoft.Office.Interop.Word;
using MSExcel = Microsoft.Office.Interop.Excel;
using Microsoft.Office.Interop.Excel;
using DataTable = System.Data.DataTable;
using System.Text.RegularExpressions;
using System.Net.Mail;
using System.Runtime.CompilerServices;
using Microsoft.Office.Interop.Word;
using MailMessage = System.Net.Mail.MailMessage;

public class MSOfficeClassV2
{
    public MSOfficeClassV2()
    {
    }

    #region Excel
    public static void Export2Excel(MSExcel.Application msXLApp,MSExcel.Workbook xlWB, DataTable dtExport, string sFile, string TabName)
    {
        MSExcel.Worksheet xlWS;

        if (File.Exists(sFile))
        {
            xlWS = xlWB.Worksheets.Add(After: xlWB.Worksheets[xlWB.Worksheets.Count]);
        }
        else
        {
            xlWS = xlWB.ActiveSheet;
        }

        xlWS.Activate();

        //Rename worksheet tab
        try
        {
            //Attempt to rename, will error to catch if tab name already exists 
            xlWS.Name = TabName;
        }
        catch
        {
            //If tab name already exists, delete it and rename
            MSExcel.Worksheet xlWS2Delete = xlWB.Worksheets[TabName];
            xlWS2Delete.Delete();
            xlWS.Name = TabName;
        }

        //Get row and column count from export data
        int columnCount = dtExport.Columns.Count;
        int rowCount = dtExport.Rows.Count;

        SetHeaderRowValues(xlWS, dtExport, columnCount);

        SetCellData(msXLApp, xlWS, dtExport, rowCount, columnCount);

        FormatWorksheet(msXLApp, xlWS, rowCount, columnCount);

        Marshal.ReleaseComObject(xlWS);

        //Save file
        if (File.Exists(sFile))
        {
            xlWB.Save();
        }
        else
        {
            xlWB.SaveAs(sFile);
        }
    }

    public static MSExcel.Application NewExcelApp()
    {
        //New application instance of Excel
        MSExcel.Application msExcelApp = new MSExcel.Application();
        msExcelApp.Visible = false;
        msExcelApp.DisplayAlerts = false;

        return msExcelApp;
    }

    public static MSExcel.Workbook newWorkbook(MSExcel.Application msXLApp, string sFile)
    {
        MSExcel.Workbook xlWB;

        if (File.Exists(sFile))
        {
             xlWB = msXLApp.Workbooks.Open(sFile);
        }
        else
        {
            xlWB = msXLApp.Workbooks.Add();
        }

        xlWB.Activate();

        return xlWB;
    }

    public static void closeWorkbook(MSExcel.Workbook xlWB)
    {
        xlWB.Close(0);
        Thread.Sleep(2000);
        Marshal.ReleaseComObject(xlWB);
    }

    public static void closeExcelApplication(MSExcel.Application msXLApp)
    {
        msXLApp.Quit();
        Marshal.ReleaseComObject(msXLApp);

        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

    private static void SetHeaderRowValues(Worksheet xlWS, DataTable dtExport, int columnCount)
    {
        //Create new object array to hold header values
        object[] Header = new object[columnCount];
        //Set each column name to object array value
        for (int h = 0; h < columnCount; h++)
        {
            Header[h] = dtExport.Columns[h].ColumnName;
        }

        //Define header row range
        MSExcel.Range headerRange = xlWS.get_Range((MSExcel.Range)xlWS.Cells[1, 1], (MSExcel.Range)xlWS.Cells[1, columnCount]);
        //Set header row values
        headerRange.Value = Header;

        FormatHeaderRow(Header, headerRange);
        
    }

    private static void FormatHeaderRow(object[] Header, MSExcel.Range headerRange)
    {
        //Format header row
        headerRange.Interior.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.LightGray);
        headerRange.Font.Bold = true;
    }

    private static void SetCellData(MSExcel.Application msExcelApp, Worksheet xlWS, DataTable dtExport, int rowCount, int columnCount)
    {
        //Create object to hold data values
        object[,] cellData = new object[rowCount, columnCount];

        //Loop rows and columns to set array values from datatable
        for(int r = 0; r < rowCount; r++)
        {
            for(int c = 0; c < columnCount; c++)
            {
                cellData[r, c] = dtExport.Rows[r][c];
            }
        }

        //Define data cell range
        MSExcel.Range dataRange = xlWS.get_Range((MSExcel.Range)xlWS.Cells[2, 1], (MSExcel.Range)xlWS.Cells[rowCount+1, columnCount]);
        //Set data range values
        dataRange.Value = cellData;
    }

    private static void FormatWorksheet(MSExcel.Application msExcelApp, Worksheet xlWS, int rowCount, int columnCount)
    {
        MSExcel.Range dataRange = xlWS.get_Range((MSExcel.Range)xlWS.Cells[1, 1], (MSExcel.Range)xlWS.Cells[rowCount + 1, columnCount]);
        xlWS.ListObjects.AddEx(XlListObjectSourceType.xlSrcRange, dataRange, Type.Missing,XlYesNoGuess.xlYes,Type.Missing).Name = "myStyle";
        xlWS.ListObjects.get_Item("myStyle").TableStyle = "TableStyleLight16";
        xlWS.Columns.AutoFit();
        xlWS.Rows.AutoFit();
        msExcelApp.ActiveWindow.SplitRow = 1;
        msExcelApp.ActiveWindow.FreezePanes = true;
    }

    #endregion
}

修复方案

1. 减少Save调用频率,避免文件锁定

当前代码每添加一个工作表就调用一次Save,100+工作表会触发100+次磁盘写入,极易导致文件系统锁定或资源耗尽。

修改方式:
移除Export2Excel方法内的Save/SaveAs代码块,改为在所有工作表添加完成后统一执行保存操作:

// 移除Export2Excel内的以下代码
// if (File.Exists(sFile))
// {
//     xlWB.Save();
// }
// else
// {
//     xlWB.SaveAs(sFile);
// }

外部调用流程示例:

var excelApp = MSOfficeClassV2.NewExcelApp();
var workbook = MSOfficeClassV2.newWorkbook(excelApp, @"D:\export.xlsx");

// 循环导出所有工作表
foreach (var subTable in subTablesList)
{
    MSOfficeClassV2.Export2Excel(excelApp, workbook, subTable, @"D:\export.xlsx", subTable.TableName);
}

// 统一保存
if (File.Exists(@"D:\export.xlsx"))
{
    workbook.Save();
}
else
{
    workbook.SaveAs(@"D:\export.xlsx");
}

MSOfficeClassV2.closeWorkbook(workbook);
MSOfficeClassV2.closeExcelApplication(excelApp);

2. 避免ListObject命名冲突

FormatWorksheet中固定使用myStyle作为ListObject名称,多工作表场景下会覆盖同名对象,引发Excel内部资源冲突。

修改方式:
为每个工作表生成唯一的ListObject名称:

private static void FormatWorksheet(MSExcel.Application msExcelApp, Worksheet xlWS, int rowCount, int columnCount)
{
    MSExcel.Range dataRange = xlWS.get_Range((MSExcel.Range)xlWS.Cells[1, 1], (MSExcel.Range)xlWS.Cells[rowCount + 1, columnCount]);
    // 结合工作表名称生成唯一标识
    string uniqueListName = $"myStyle_{xlWS.Name.Replace(" ", "_")}";
    var listObj = xlWS.ListObjects.AddEx(XlListObjectSourceType.xlSrcRange, dataRange, Type.Missing,XlYesNoGuess.xlYes,Type.Missing);
    listObj.Name = uniqueListName;
    listObj.TableStyle = "TableStyleLight16";
    
    xlWS.Columns.AutoFit();
    xlWS.Rows.AutoFit();
    msExcelApp.ActiveWindow.SplitRow = 1;
    msExcelApp.ActiveWindow.FreezePanes = true;

    // 释放子对象
    Marshal.ReleaseComObject(dataRange);
    Marshal.ReleaseComObject(listObj);
}

3. 彻底释放Excel子对象

原有代码未释放Range、ListObject等子对象,大量创建工作表时会导致资源泄漏,引发Excel进程异常。

补充修改:
在SetHeaderRowValues中释放headerRange对象:

private static void SetHeaderRowValues(Worksheet xlWS, DataTable dtExport, int columnCount)
{
    object[] Header = new object[columnCount];
    for (int h = 0; h < columnCount; h++)
    {
        Header[h] = dtExport.Columns[h].ColumnName;
    }

    MSExcel.Range headerRange = xlWS.get_Range((MSExcel.Range)xlWS.Cells[1, 1], (MSExcel.Range)xlWS.Cells[1, columnCount]);
    headerRange.Value = Header;

    FormatHeaderRow(Header, headerRange);
    
    // 释放headerRange
    Marshal.ReleaseComObject(headerRange);
}

4. 清理临时文件与检查权限

  • 导出前清理目标目录下的Excel临时文件(以.tmp结尾的文件);
  • 确认应用运行用户对导出目录拥有完整的读写权限;
  • 确保导出磁盘有足够剩余空间。

内容的提问来源于stack exchange,提问作者Jacob G.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 00:20:27