用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。