首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

ASP.NET 伺服器端產出 Excel 的最佳實踐:COM 互操作與資源釋放完整指南

     編輯:關於C語言

核心挑戰:Excel.exe 殭屍進程

使用 Microsoft.Office.Interop.Excel 產生報表時,若未正確釋放 COM 物件,伺服器將殘留大量 EXCEL.EXE 進程,耗盡記憶體與授權。

關鍵釋放模式(必須嚴格遵循)

// 正確關閉與釋放順序
workbook.Close(false, Type.Missing, Type.Missing);
app.Workbooks.Close();
app.Quit();

// 由內而外釋放 COM 物件
Marshal.ReleaseComObject(range);
Marshal.ReleaseComObject(worksheet);
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(app);

// 明確設為 null
range = worksheet = workbook = app = null;

// 強制 GC 回收 RCW
GC.Collect();
GC.WaitForPendingFinalizers();

原則:每個透過 Interop 取得的 COM 物件(Application、Workbook、Worksheet、Range 等)都必須對應一次 ReleaseComObject,順序由細顆粒度到粗顆粒度。

完整產出 Excel 報表範例

public void GenerateExcelReport(string storedProc, SqlParameter[] parameters)
{
    string folder = Server.MapPath("~/Reports/");
    CleanOldFiles(folder, TimeSpan.FromHours(1)); // 清理 1 小時前檔案

    Application app = null;
    Workbook wb = null;
    Worksheet ws = null;
    Range rng = null;

    try
    {
        GC.Collect(); // 預先清理
        app = new Application { Visible = false, DisplayAlerts = false };
        wb = (Workbook)app.Workbooks.Add(Type.Missing);
        ws = (Worksheet)wb.ActiveSheet;

        // 取得資料
        using (var reader = new StoredProcRunner(connStr, storedProc, parameters).ExecuteReader())
        {
            // 寫入標題列
            for (int i = 0; i < reader.FieldCount; i++)
                ws.Cells[1, i + 1] = reader.GetName(i);

            // 寫入資料列
            int row = 2;
            while (reader.Read())
            {
                for (int i = 0; i < reader.FieldCount; i++)
                    ws.Cells[row, i + 1] = reader.GetValue(i)?.ToString();
                row++;
            }
        }

        // 格式化
        rng = ws.get_Range("A1", $"{(char)('A' + reader.FieldCount - 1)}1");
        rng.Font.Bold = true;
        rng.VerticalAlignment = XlVAlign.xlVAlignCenter;
        rng.EntireColumn.AutoFit();

        // 儲存
        string fileName = $"Report_{DateTime.Now:yyyyMMdd_HHmmss}_{Guid.NewGuid():N}.xlsx";
        string fullPath = Path.Combine(folder, fileName);
        wb.SaveAs(fullPath, XlFileFormat.xlOpenXMLWorkbook);

        // 提供下載連結
        string url = $"{Request.Url.GetLeftPart(UriPartial.Authority)}/Reports/{fileName}";
        Response.Write($"下載報表");
    }
    catch (Exception ex)
    {
        LogError(ex);
        Response.Write("報表產生失敗:" + ex.Message);
    }
    finally
    {
        // 嚴格釋放資源
        ReleaseComObjects(wb, app, ws, rng);
    }
}

private void ReleaseComObjects(params object[] comObjects)
{
    foreach (var obj in comObjects)
    {
        if (obj != null && Marshal.IsComObject(obj))
        {
            try { Marshal.ReleaseComObject(obj); } catch { }
        }
    }
    GC.Collect();
    GC.WaitForPendingFinalizers();
}

現代替代方案:免 Office、高效能、跨平台

強烈建議新專案採用純 .NET 程式庫,完全避免 COM 互操作問題:

  • ClosedXML:API 直覺,支援 .xlsx,基於 OpenXML SDK
  • EPPlus(v5 以下免費,v6+ 商業授權):功能豐富,效能極佳
  • NPOI:同時支援 .xls/.xlsx,Apache POI 移植版
  • DocumentFormat.OpenXml:微軟官方底層 SDK,控制最精細

ClosedXML 範例

using (var wb = new XLWorkbook())
{
    var ws = wb.Worksheets.Add("Report");
    ws.Cell(1, 1).InsertTable(dataTable); // 一行寫入 DataTable
    ws.Columns().AdjustToContents();
    
    using (var ms = new MemoryStream())
    {
        wb.SaveAs(ms);
        return File(ms.ToArray(), 
            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
            "Report.xlsx");
    }
}

無需安裝 Office、無 COM 物件洩漏、支援 Linux/Docker 容器化部署、效能提升 10-100 倍。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved