用NPOI建立Excel Stream,解決 Exception:由於另一個處理序正在使用檔案[xxxxx.xlsx]所以無法存取該檔案。

用NPOI建立Excel Stream,解決 Exception:由於另一個處理序正在使用檔案[xxxxx.xlsx]所以無法存取該檔案。

這幾天幫公司開發一個排程寄信的功能

原本就法大概如下

1.撈出Data並站存於DataTable(DT)

2.把DT的資料直接產出成實體Excel檔

3.把步驟2.的實體檔的完整路徑傳給System.Net.Mail.Attachment當參數

var Attach = new List<Attachment>();
Attach.Add(new Attachment(attachFullFileName));

4.執行SendMail()發送郵件

通常第一次執行都沒有什麼問題,但第二次執行時就會發生下面的問題,但也不是每次都會出現這個問題。且只有檔名相同時才會有問題,如果檔名後面加產黨時間或流水號就不會有問題。

System.IO.IOException: 由於另一個處理序正在使用檔案 'D:\xxxxx\xxxxx.xlsx',所以無法存取該檔案。

 

於是就下中斷點來逐步檢查Debug,後來發現問題是出現在底層用來產出Excel實體檔的WriteToExcel這段程式,裡面有一個File.Delete()的動作,每次都是在這裡出現Exception。看字面應該是檔案被Lock住了,導致刪除檔案時無法刪除而出現Exception,但已經有包using了,應該是不會出現檔案被使用中的問題,而且也確定fs.Close()跟fs.Dispose()都有執行到,到目前為止都還不知道為何會出現此問題,不過我在想問題應該是跟FileMode.OpenOrCreate有關吧(OpenOrCreate:指定作業系統應開啟檔案,如果檔案不存在,則建立新檔案),上一次的fs不知道為什麼原因沒有關閉,導致下一次執行Delete時把檔案Lock住。

FileMode狀態說明請參考:Stream. FileStream. MemoryStream的區別

        public void WriteToExcel(string fileName)
        {
            CheckFileIsExists(fileName);

            using (var fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write))
            {

                Workbook.Write(fs);

                // XLSX
                if (ExcelType == "XLSX")
                {
                    fs.Close();
                    fs.Dispose();
                }
                else
                {
                    fs.Flush();
                }
            }
        }

        public void CheckFileIsExists(string fileName)
        {
            if (File.Exists(fileName))
            {
                File.Delete(fileName);
            }
        }

 

在無法解決這個問題的情況下,山不轉路轉,查到Attachment類別的建構子參數除了傳檔案完整路徑也可以直接傳Stream,於是就改成不採用產出實體檔案再提供完整路徑給Attachment的方式來夾帶附件,改採用透過NPOI把Excel資料內容寫到MemoryStream(繼承Stream)在把MemoryStream當參數傳給Attachment的方式來避免掉因為處理實體檔的Create & Delete而產生檔案Lock的問題。

 

順便附上用NPOI把Excel資料寫入至Stream的作法

參考文章:ASP.NET 案例分享:從 NPOI 匯出的 Excel 資料流建立附件並寄送 & 使用NPOI產生EXCEL檔

改成用Stream的寫法後,在寫入Stream時又一直出現無法存取關閉的串流錯誤的問題,所以才有先把Stream轉成Byte Array,再把Byte Array倒回去Stream的奇怪作法(可參考:NPOI write to Stream get Can't open closed Stream error),不過後來是有順利解決問題。

        public static Stream RenderDataTableToExcelStream(Dictionary<string, DataTable> dataInfo)
        {
            var workbook = new XSSFWorkbook();
            foreach (var Item in dataInfo)
            {
                var SheetName = Item.Key;
                var SourceData = Item.Value;                
                var Sheet = (XSSFSheet)workbook.CreateSheet(SheetName);
                var HeaderRow = (XSSFRow)Sheet.CreateRow(0);
                // handling header.
                foreach (DataColumn column in SourceData.Columns)
                {
                    HeaderRow.CreateCell(column.Ordinal).SetCellValue(column.ColumnName);
                }
                // handling value.
                int rowIndex = 1;
                foreach (DataRow row in SourceData.Rows)
                {
                    var dataRow = (XSSFRow)Sheet.CreateRow(rowIndex);
                    foreach (DataColumn column in SourceData.Columns)
                    {
                        dataRow.CreateCell(column.Ordinal).SetCellValue(row[column].ToString());
                    }
                    rowIndex++;
                }
            }

            var stream = new MemoryStream();
            var ArrayMemoryStream = new byte[] { };
            using (stream)
            {
                workbook.Write(stream);
                stream.Flush();
                ArrayMemoryStream = stream.ToArray();                
            }
            var MS = new MemoryStream(ArrayMemoryStream);

            return MS;
        }

 

總而言之,整個過程不算是很順利,但幸好後來還是都有把問題處理掉,這兩個問題其實以前好像也有遇到過,但沒記錄下來,趁這次機會一起出現就順便記錄下來。

  • System.IO.IOException: 由於另一個處理序正在使用檔案 'D:\xxxxx\xxxxx.xlsx',所以無法存取該檔案。=>改採直接把資料寫入Stream,不產實體檔
  • 無法存取關閉的串流錯誤的問題=>先把Steam轉ByteArray在轉回Stream。