C# Excel操作記錄 及 程式碼優化考慮
最近,因為有需求,找了很多C#操作Excel的資料來學習,完了準備在這裡做個記錄,以備後日不時之需。
我看到的,操作Excel的方式有四種:
- com元件
- OleDb
- npoi
- epplus
com元件
首先,com 元件的方式指的是利用 office 的 Excel 元件對Excel檔案進行操作,這種方式需要電腦上安裝有Excel,專案中也要新增相關引用,專案執行時也要啟動Excel程序。
據說,這種方式的功能很強大,說是Excel能實現的功能都能夠通過這種方法在程式碼裡進行設定,反正我是沒有驗證過,因為它實在是太慢了。其他方法1s就可以搞定的,用它穩定在8.7s...
OleDb
使用Microsoft Jet 提供程式用於連線到 Excel 工作簿,將Excel檔案作為資料來源來讀寫,這種方法讀寫都相當快,就是不是很靈活。
它需要使用指定的連線字串初始化 System.Data.OleDb.OleDbConnection 類的新例項,.xls檔案 和 .xlsx檔案的連線字串並不一樣。
case ".xls": strConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + pathName + ";Extended Properties='Excel 8.0;HDR=Yes;IMEX=2;'"; break; case ".xlsx": strConn = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + pathName + ";Extended Properties='Excel 12.0;HDR=Yes;IMEX=2;'"; break;
上面的HDR用於判斷是否將第一行作為資料讀取(個人感覺應該只在讀操作有用),IMEX用於判斷是讀取還是寫入,這個需要自己嘗試,我只能說,我在寫入操作的時候將其設為0,讀取的時候設為1,你也可以設為2,錯了再調整...
它能返回所有的sheet的名稱,以及單個sheet的所有列名,這有時是相當有用的。
#region 獲取Excel檔案的表名 返回list集合 /// <param name="excelFileName">路徑名</param> /// <returns></returns> public static List<string> GetExcelTableName(OleDbConnection conn, string pathName) { List<string> tableName = new List<string>(); if (File.Exists(pathName)) { //conn.Open(); System.Data.DataTable dt = conn.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Tables, null); string strSheetTableName = null; foreach (System.Data.DataRow row in dt.Rows) { strSheetTableName = row["TABLE_NAME"].ToString(); //過濾無效SheetName if (strSheetTableName.Contains("$") && strSheetTableName.Replace("'", "").EndsWith("$")) { strSheetTableName = strSheetTableName.Replace("'", ""); //可能會有 '1X$' 出現 strSheetTableName = strSheetTableName.Substring(0, strSheetTableName.Length - 1); tableName.Add(strSheetTableName); } } } return tableName; } #endregion
#region 獲取EXCEL工作表的列名 返回list集合
/// <param name="Path">Excel路徑名</param>
/// <returns></returns>
public static List<string> getExcelFileInfo(string pathName)
{
string strConn;
List<string> list_ColumnName = new List<string>();
FileInfo file = new FileInfo(pathName);
if (!file.Exists) { throw new Exception("檔案不存在"); }
string extension = file.Extension;
switch (extension)
{
case ".xls":
strConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + pathName + ";Extended Properties='Excel 8.0;HDR=Yes;IMEX=2;'";
break;
case ".xlsx":
strConn = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + pathName + ";Extended Properties='Excel 12.0;HDR=Yes;IMEX=2;'";
break;
default:
strConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + pathName + ";Extended Properties='Excel 8.0;HDR=Yes;IMEX=1;'";
break;
}
System.Data.OleDb.OleDbConnection conn = new System.Data.OleDb.OleDbConnection(strConn);
conn.Open();
System.Data.DataTable table = conn.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Tables, new object[] { null, null, null, null });
string TableName = null;
string ColumnName = null;
foreach (System.Data.DataRow drow in table.Rows)
{
TableName = drow["Table_Name"].ToString();
if (TableName.Contains("$") && TableName.Replace("'", "").EndsWith("$"))
{
System.Data.DataTable tableColumns = conn.GetOleDbSchemaTable(System.Data.OleDb.OleDbSchemaGuid.Columns, new object[] { null, null, TableName, null });
foreach (System.Data.DataRow drowColumns in tableColumns.Rows)
{
ColumnName = drowColumns["Column_Name"].ToString();
list_ColumnName.Add(ColumnName);
}
}
}
return list_ColumnName;
}
#endregion
讀寫的部分準備用copy來進行說明,雖然不會用到。
#region 讀取Excel,將第一行作為標題,其他行作為資料,再將其匯出到Excel,對空白行進行處理
public static bool copy(string copyPath, string pastePath, string copy_sheetName)
{
DataSet ds = new DataSet();
OleDbConnection copy_objConn = null;//Excel的連線
OleDbConnection paste_objConn = null;//Excel的連線
try
{
string strExtension = System.IO.Path.GetExtension(copyPath);//獲取副檔名
string strFileName = System.IO.Path.GetFileName(copyPath);//獲取檔名
switch (strExtension)
{
case ".xls":
copy_objConn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + copyPath
+ ";" + "Extended Properties=\"Excel 8.0;HDR=YES;IMEX=1;\"");
break;
case ".xlsx":
copy_objConn = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + copyPath
+ ";" + "Extended Properties=\"Excel 12.0;HDR=YES;IMEX=1;\"");
break;
default:
copy_objConn = null;
break;
}
if (copy_objConn == null)
{
return false;
}
copy_objConn.Open();
string strSql = "select * from [" + copy_sheetName + "] where [系統物料號] is not null";
OleDbCommand objCmd = new OleDbCommand(strSql, copy_objConn);//獲取Excel指定Sheet表中的資訊
OleDbDataAdapter myData = new OleDbDataAdapter(strSql, copy_objConn);
myData.Fill(ds, copy_sheetName);//填充資料
DataTable dt = ds.Tables[0];
strExtension = System.IO.Path.GetExtension(pastePath);//獲取副檔名
strFileName = System.IO.Path.GetFileName(pastePath);//獲取檔名
switch (strExtension)
{
case ".xls":
paste_objConn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + pastePath
+ ";" + "Extended Properties=\"Excel 8.0;HDR=YES;IMEX=0;\"");
break;
case ".xlsx":
paste_objConn = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + pastePath
+ ";" + "Extended Properties=\"Excel 12.0;HDR=YES;IMEX=0;\"");
break;
default:
paste_objConn = null;
break;
}
if (paste_objConn == null)
{
return false;
}
paste_objConn.Open();
StringBuilder strSQL = new StringBuilder();
System.Data.OleDb.OleDbCommand cmd;
string tableName = dt.TableName.Substring(0, dt.TableName.Length - 1);
try
{
List<string> list = GetExcelTableName(paste_objConn, pastePath);
if (list.Contains(tableName))
{
//覆蓋檔案時可能會出現Table 'Sheet1' already exists.所以這裡先刪除了一下
cmd = new System.Data.OleDb.OleDbCommand(string.Format("drop table {0}", tableName), paste_objConn);
cmd.ExecuteNonQuery();
}
}
catch (Exception e)
{
Console.WriteLine("drop table failed!" + e.Message);
}
//建立表格欄位
strSQL.Append("CREATE TABLE ").Append("[" + tableName + "]");
strSQL.Append("(");
for (int i = 0,int dt_column_count = dt.Columns.Count; i < dt_column_count ; i++)
{
strSQL.Append("[" + dt.Columns[i].ColumnName + "] text,");
}
strSQL = strSQL.Remove(strSQL.Length - 1, 1);
strSQL.Append(")");
cmd = new System.Data.OleDb.OleDbCommand(strSQL.ToString(), paste_objConn);
cmd.ExecuteNonQuery();
//新增資料
StringBuilder strvalue = null;
for (int i = 0,int dt_row_count = dt.Rows.Count; i < dt_row_count; i++)
{
strSQL.Clear();
strvalue = new StringBuilder();
for (int j = 0; j < dt.Columns.Count; j++)
{
strvalue.Append("'" + dt.Rows[i][j].ToString() + "'");
if (j != dt.Columns.Count - 1)
{
strvalue.Append(",");
}
}
cmd.CommandText = strSQL.Append(" insert into [" + dt.TableName.Substring(0, dt.TableName.Length - 1) + "] values (").Append(strvalue).Append(")").ToString();
cmd.ExecuteNonQuery();
}
}
catch (Exception e)
{
Console.WriteLine("copy失敗!" + e.Message);
return false;
}
finally
{
copy_objConn.Close();
paste_objConn.Close();
}
return true;
}
#endregion
有一點需要注意的是,win32 平臺是可以正常用的,any CPU 平臺是不可以的,需要安裝微軟的程式等操作。
因為這個原因,我也沒有找有沒有操作格式的方法,有需要的自己找找看。
NPOI
這是花費時間最久的外掛,用完只能說真的不好用。
想要用這個外掛的需要在NuGet程式包裡搜尋安裝到專案中。
讀寫都不是很慢,但是資料量太大的無法處理,大概30000 * 20 的規模已經不能勝任了。
它對於.xls檔案 和 .xlsx檔案的處理也是不一樣的:
ISheet sheet = null;
IWorkbook workbook = null;
switch (strExtension)
{
case ".xls":
workbook = new HSSFWorkbook(fs);
sheet = workbook.GetSheet(sheetName);
break;
case ".xlsx":
workbook = new XSSFWorkbook(fs);
sheet = workbook.GetSheet(sheetName);
break;
default:
break;
}
npoi讀取Excel需要對內容進行型別判斷
private static object GetValueType(ICell cell, IWorkbook workbook)
{
if (cell == null)
return null;
switch (cell.CellType)
{
case CellType.Blank: //BLANK:
return null;
case CellType.Boolean: //BOOLEAN:
return cell.BooleanCellValue;
case CellType.Numeric: //NUMERIC:
return cell.NumericCellValue;
case CellType.String: //STRING:
return cell.StringCellValue;
case CellType.Error: //ERROR:
return cell.ErrorCellValue;
case CellType.Formula: //FORMULA:
default:
return "=" + cell.CellFormula;
}
}
現在來說遇到的第一個問題,比如說,我希望讀取A1的內容怎麼辦,用下面的方法可以嗎?
IRow row1 = sheet.GetRow(0);
ICell cell1 = row1.GetCell(0);
object value = GetValueType(cell1, workbook);
一般情況,是可以的,但是如果是公式,出來的結果並不是希望的結果,對上面的方法進行修改:
private static object GetValueType(ICell cell, IWorkbook workbook)
{
if (cell == null)
return null;
switch (cell.CellType)
{
case CellType.Blank: //BLANK:
return null;
case CellType.Boolean: //BOOLEAN:
return cell.BooleanCellValue;
case CellType.Numeric: //NUMERIC:
return cell.NumericCellValue;
case CellType.String: //STRING:
return cell.StringCellValue;
case CellType.Error: //ERROR:
return cell.ErrorCellValue;
case CellType.Formula: //FORMULA:
IFormulaEvaluator evaluator = null;
if (workbook is HSSFWorkbook)
evaluator = new HSSFFormulaEvaluator(workbook);
else
evaluator = new XSSFFormulaEvaluator(workbook);
cell = evaluator.EvaluateInCell(cell);
return cell.NumericCellValue;
default:
return "=" + cell.CellFormula;
}
}
修改過後,會發現,一部分公式成功獲得結果,一部分公式提示捕獲異常,在進行修改:
private static object GetValueType(ICell cell, IWorkbook workbook)
{
if (cell == null)
return null;
switch (cell.CellType)
{
case CellType.Blank: //BLANK:
return null;
case CellType.Boolean: //BOOLEAN:
return cell.BooleanCellValue;
case CellType.Numeric: //NUMERIC:
return cell.NumericCellValue;
case CellType.String: //STRING:
return cell.StringCellValue;
case CellType.Error: //ERROR:
return cell.ErrorCellValue;
case CellType.Formula: //FORMULA:
IFormulaEvaluator evaluator = null;
if (workbook is HSSFWorkbook)
evaluator = new HSSFFormulaEvaluator(workbook);
else
evaluator = new XSSFFormulaEvaluator(workbook);
cell = evaluator.EvaluateInCell(cell);
try
{
return cell.NumericCellValue;
}
catch
{
return cell.StringCellValue;
}
default:
return "=" + cell.CellFormula;
}
}
這樣應該能在大多數情況下,獲取我們希望的值了。
第二個問題,同一個sheet中資料區域中間隔著很多空行,不知道你們有沒有遇到過這種情況,npoi讀取Excel檔案的行數時,即使該行沒有資料,但是隻要最初的行高被動過,也或被讀到,即,把3000行的行高改了,但是其實只有100行資料,它計算的行數就會是3000,如果你知道只需要讀取100行,但是如果從200行開始又有100行資料怎麼辦?如果說這個也知道,那當我沒說。事實上,雖然npoi統計出來有那麼多行,但是當你獲取空行時就會報異常,提示你引用物件沒有獲取例項,大概是這麼個意思,這說明sheet裡是沒有存這些空行的資訊的,除了行數。如果你對Excel結構不太清楚,那麼就需要在用異常做流程處理,雖然這樣做實際上效率很低。
/// <summary>
/// Excel匯入成Datable
/// </summary>
/// <param name="strExcelPath">匯入路徑(包含檔名與副檔名)</param>
/// <returns></returns>
private static DataTable ExcelToDataTable(string strExcelPath, string sheetName)
{
DataTable dataTable = new DataTable();
FileStream fs = null;
try
{
//獲取副檔名
string strExtension = System.IO.Path.GetExtension(strExcelPath);
using (fs = new FileStream(strExcelPath, FileMode.Open, FileAccess.Read))
{
ISheet sheet = null;
IWorkbook workbook = null;
switch (strExtension)
{
case ".xls":
workbook = new HSSFWorkbook(fs);
sheet = workbook.GetSheet(sheetName);
break;
case ".xlsx":
workbook = new XSSFWorkbook(fs);
sheet = workbook.GetSheet(sheetName);
break;
default:
break;
}
if (sheet == null)
Console.WriteLine("檔案字尾名錯誤!");
//表頭
IRow header = sheet.GetRow(sheet.FirstRowNum);
List<int> columns = new List<int>();
object obj = null;
for (int i = 0, int dt_column_count = header.LastCellNum; i < dt_column_count; i++)
{
obj = GetValueType(header.GetCell(i), workbook); string a = header.GetCell(0).StringCellValue;
if (obj == null || obj.ToString() == string.Empty)
{
dataTable.Columns.Add(new DataColumn("Columns" + i.ToString()));
//continue;
}
else
dataTable.Columns.Add(new DataColumn(obj.ToString()));
columns.Add(i);
}
//資料
object value = null;
DataRow dr = null;
for (int i = sheet.FirstRowNum + 1, int dt_row_count = sheet.LastRowNum; i <= dt_row_count; i++)
{
try
{
value = GetValueType(sheet.GetRow(i).GetCell(0), workbook);
}
catch
{
continue;
}
if (value != null)
{
dr = dataTable.NewRow();
bool hasValue = false;
foreach (int j in columns)
{
dr[j] = GetValueType(sheet.GetRow(i).GetCell(j), workbook);
if (dr[j] != null && dr[j].ToString() != string.Empty)
hasValue = true;
}
if (hasValue)
dataTable.Rows.Add(dr);
}
}
return dataTable;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return dataTable;
}
finally
{
if(fs != null)
fs.Close();
}
}
讀取的情況應該沒什麼大問題的,下面貼上Excel轉datatable的方法,也可以轉到集合裡,按需求做些修改就行了。
第三個問題,關於這個寫入的問題,實際上我到最終也沒什麼好方法,如果有人知道,歡迎幫我解惑。
先貼上一段寫入的方法:
/// <summary>
/// Datable匯出成Excel
/// </summary>
/// <param name="dt">資料來源</param>
/// <param name="strExcelPath">匯出路徑(包括檔名與副檔名)</param>
/// <param name="sheetName">生成的sheet的name</param>
/// <returns></returns>
private static bool TableToExcel(DataTable dt, string strExcelPath, string sheetName)
{
//獲取副檔名
string strExtension = System.IO.Path.GetExtension(strExcelPath);
//string strFileName = System.IO.Path.GetFileName(strExcelPath);
IWorkbook workbook = null;
switch (strExtension)
{
case ".xls":
workbook = new HSSFWorkbook();
break;
case ".xlsx":
workbook = new XSSFWorkbook();
break;
default:
break;
}
if (workbook == null)
return false;
ISheet sheet = workbook.CreateSheet(sheetName);
//表頭
ICell cell = null;
IRow row = sheet.CreateRow(0);
for (int i = 0,int column_count = dt.Columns.Count; i < column_count; i++)
{
cell = row.CreateCell(i);
cell.SetCellValue(dt.Columns[i].ColumnName);
}
//資料
IRow row1 = null;
ICell cell = null;
for (int i = 0, int row_count = dt.Rows.Count; i < row_count; i++)
{
row1 = sheet.CreateRow(i + 1);
for (int j = 0, int column_count = dt.Columns.Count; j < column_count; j++)
{
cell = row1.CreateCell(j);
cell.SetCellValue(dt.Rows[i][j].ToString());
}
}
//轉為位元組陣列
MemoryStream stream = new MemoryStream();
workbook.Write(stream);
var buf = stream.ToArray();
//儲存為Excel檔案
FileMode mode = FileMode.OpenOrCreate;
FileAccess access = FileAccess.ReadWrite;
FileShare share = FileShare.ReadWrite;
using (FileStream fs = new FileStream(strExcelPath, mode, access, share))
{
fs.Write(buf, 0, buf.Length);
fs.Flush();
fs.Close();
}
return true;
}
事實上,我一開始在資料庫裡讀了一些資料然後寫入Excel,成功了,我就沒管了。
知道我在一系列讀取後在進行寫入時出現了問題,整個Excel檔案都無法打開了。我經過一系列除錯,發現讀取之後都是沒問題的,寫入的最後出現問題,即FileStream出現後的那幾句程式碼,我懷疑過很多地方,比如之前的fs未close,實際上不需要手動close;再比如new FileStream時的引數,這讓我又發現了一個問題,我進行寫入操作的時候會將原先的刪除再新建,並且更改引數並不起效果,我猜測應該將原有檔案作為引數傳進來,找了一下確實是這個問題,在已存在的Excel檔案新增sheet的完整程式碼:
/// <summary>
/// Datable匯出成Excel
/// </summary>
/// <param name="dt">資料來源</param>
/// <param name="strExcelPath">匯出路徑(包括檔名與副檔名)</param>
/// <param name="sheetName">生成的sheet的name</param>
/// <param name="flag">是新建or開啟:true 為 create;false 為 open</param>
/// <returns></returns>
private static bool TableToExcel(DataTable dt, string strExcelPath, string sheetName, bool flag)
{
//獲取副檔名
string strExtension = System.IO.Path.GetExtension(strExcelPath);
//string strFileName = System.IO.Path.GetFileName(strExcelPath);
IWorkbook workbook = null;
switch (strExtension)
{
case ".xls":
if(flag)
workbook = new HSSFWorkbook();
else
workbook = new HSSFWorkbook(File.OpenRead(strExcelPath));
break;
case ".xlsx":
if(flag)
workbook = new XSSFWorkbook();
else
workbook = new XSSFWorkbook(File.OpenRead(strExcelPath));
break;
default:
break;
}
if (workbook == null)
return false;
ISheet sheet = workbook.CreateSheet(sheetName);
//表頭
IRow row = sheet.CreateRow(0);
ICell cell = null;
for (int i = 0, int column_count = dt.Columns.Count; i < column_count; i++)
{
cell = row.CreateCell(i);
cell.SetCellValue(dt.Columns[i].ColumnName);
}
//資料
IRow row1 = null;
ICell cell = null;
for (int i = 0, int row_count = dt.Rows.Count; i < row_count; i++)
{
row1 = sheet.CreateRow(i + 1);
for (int j = 0, int column_count = dt.Columns.Count; j < column_count; j++)
{
cell = row1.CreateCell(j);
cell.SetCellValue(dt.Rows[i][j].ToString());
}
}
//轉為位元組陣列
MemoryStream stream = new MemoryStream();
workbook.Write(stream);
var buf = stream.ToArray();
//儲存為Excel檔案
FileMode mode = FileMode.OpenOrCreate;
FileAccess access = FileAccess.ReadWrite;
FileShare share = FileShare.ReadWrite;
using (FileStream fs = new FileStream(strExcelPath, mode, access, share))
{
fs.Write(buf, 0, buf.Length);
fs.Flush();
fs.Close();
}
return true;
}
雖然上面程式碼可以新增sheet,並且原有的sheet依然存在,但是這有個前提條件:原先存在的sheet需要時通過npoi新增的,也就是說通過office新建的sheet,上面的方法是不行的,會導致檔案損壞,無法開啟。
為了解決這個問題,我看了很多資料,據說是因為npoi寫入會在末尾新增一個標誌,那麼可以猜測一下,是不是給自己新建的sheet加一個這樣的標誌就能解決問題?我是沒有進行這個測試,最後是新建了一個Excel檔案。有哪位大神知道這種方法或者其他方法解決這個問題的,歡迎指導。
最後說一下,這個方式是可以設定單元格格式的,下面貼上一部分程式碼,對第一行設定格式:
//設定列寬
sheet.SetColumnWidth(0,25 * 256);
sheet.SetColumnWidth(1, 45 * 256);
sheet.SetColumnWidth(2, 10 * 256);
sheet.SetColumnWidth(3, 6 * 256);
sheet.SetColumnWidth(4, 20 * 256);
sheet.SetColumnWidth(5, 20 * 256);
sheet.SetColumnWidth(6, 20 * 256);
sheet.SetColumnWidth(7, 20 * 256);
//字型
IFont font = workbook.CreateFont();
font.IsBold = true; //字型為粗體
font.FontName = "等線";
//建立單元格格式
IDataFormat dataFormat = workbook.CreateDataFormat();
//ICellStyle style1 = workbook.CreateCellStyle();
//style1.DataFormat = dataFormat.GetFormat("0"); //精度到整數
//ICellStyle style2 = workbook.CreateCellStyle();
//style2.DataFormat = dataFormat.GetFormat("0.00%"); //百分數
//ICellStyle style3 = workbook.CreateCellStyle();
//style3.SetFont(font);
//ICellStyle style4 = workbook.CreateCellStyle();
//style4.FillBackgroundColor = IndexedColors.White.Index;
//style4.FillForegroundColor = IndexedColors.Black.Index;
//style4.FillPattern = FillPattern.SolidForeground;
//表頭 style : 字型粗體,背景色為藍,居中對齊
ICellStyle style_header = workbook.CreateCellStyle();
style_header.SetFont(font);
style_header.FillBackgroundColor = IndexedColors.White.Index;
style_header.Alignment = HorizontalAlignment.Center;
//style_header.FillForegroundColor = IndexedColors.LightBlue.Index;
style_header.FillForegroundColor = (short)44;
style_header.FillPattern = FillPattern.SolidForeground;
IRow row = sheet.CreateRow(0);
ICell cell = null;
for (int i = 0, int count = firstRowName.Count; i < count; i++)
{
cell = row.CreateCell(i);
cell.SetCellValue(firstRowName[i]);
cell.CellStyle = style_header;
}
epplus
這種方法挺不錯的,但是由於某些原因並沒有深入瞭解,不過資料量比npoi強,而且沒那麼多問題。
放一個比較全的測試方法吧,裡面進行了很多功能測試,我自己測試沒通過的都有標誌,沒標註的應該都是可以成功的。
public static Boolean Load_Main()
{
FileInfo newFile = new FileInfo(System.AppDomain.CurrentDomain.BaseDirectory + "test1.xlsx");
using (ExcelPackage package = new ExcelPackage(newFile)) //建立Excel
{
#region 獲取sheet
if (package.Workbook.Worksheets["test1"] != null)
{
package.Workbook.Worksheets.Delete("test1");//刪除工作表
}
//建立一個新的sheet
ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("test1");
//獲取excel中的一個sheet用於後續操作(通過模板寫資料的可用此方法)
ExcelWorksheet worksheet2 = package.Workbook.Worksheets["Sheet1"];
if (package.Workbook.Worksheets["hidesheet"] != null)
{
package.Workbook.Worksheets.Delete("hidesheet");//刪除工作表
}
ExcelWorksheet hideSheet = package.Workbook.Worksheets.Add("hidesheet");
#endregion
#region 新增資料
/* Epplus中給單元格賦值非常簡單,兩種方法:(ps:Epplus的所有行列數都是以1開始的)
* 1. worksheet.Cells[1, 1].Value = "名稱";//直接指定行列數進行賦值
* 2. worksheet.Cells["A1"].Value = "名稱";//直接指定單元格進行賦值
*/
worksheet.Cells[1, 1].Value = "名稱";
worksheet.Cells[1, 2].Value = "單價";
worksheet.Cells[1, 3].Value = "銷量";
worksheet.Cells[2, 1].Value = "洗衣機";
worksheet.Cells[2, 2].Value = 1000;
worksheet.Cells[2, 3].Value = 1000;
worksheet.Cells[3, 1].Value = "空調";
worksheet.Cells[3, 2].Value = 2300;
worksheet.Cells[3, 3].Value = 1760;
worksheet.Cells[4, 1].Value = "冰箱";
worksheet.Cells[4, 2].Value = 3400;
worksheet.Cells[4, 3].Value = 1530;
worksheet.Cells[5, 1].Value = "電視機";
worksheet.Cells[5, 2].Value = 7800;
worksheet.Cells[5, 3].Value = 2700;
#endregion
#region 公式應用
//乘法公式,第二列乘以第三列的值賦值給第四列
worksheet.Cells["E2"].Formula = "B2*C2";//單個單元格
worksheet.Cells["D2:D5"].Formula = "B2*C2";//多個單元格
//自動求和 第二列中,將2,3,4,5行求和值賦給第六行
//worksheet2.Cells[6, 2, 6, 4].Formula = string.Format("SUBTOTAL(9,{0})", new ExcelAddress(2, 2, 5, 2).Address);
worksheet.Cells["B7:D7"].Formula = string.Format("SUBTOTAL(9,{0})", new ExcelAddress(2, 2, 5, 2).Address);
#endregion
#region 設定單元格格式
worksheet.Cells[5, 3].Style.Numberformat.Format = "#,##0.00";//這是保留兩位小數
#endregion
#region 設定單元格對齊方式
using (ExcelRange range = worksheet.Cells[1, 1, 5, 3])
{
range.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;//水平居中
range.Style.VerticalAlignment = ExcelVerticalAlignment.Center;//垂直居中
}
//worksheet.Cells[1, 4, 1, 5].Merge = true;//合併單元格
//worksheet.Cells.Style.WrapText = true;//自動換行
#endregion
#region 設定單元格字型樣式
using (ExcelRange range = worksheet.Cells[1, 1, 1, 3])
{
range.Style.Font.Bold = true;
range.Style.Font.Color.SetColor(Color.White);
range.Style.Font.Name = "微軟雅黑";
range.Style.Font.Size = 12;
range.Style.Fill.PatternType = ExcelFillStyle.Solid;
range.Style.Fill.BackgroundColor.SetColor(Color.FromArgb(128, 128, 128));
}
#endregion
#region 設定單元格背景樣式
worksheet.Cells[1, 1].Style.Fill.PatternType = ExcelFillStyle.Solid;
worksheet.Cells[1, 1].Style.Fill.BackgroundColor.SetColor(Color.FromArgb(128, 128, 128));//設定單元格背景色
#endregion
#region 設定單元格邊框,兩種方法
//worksheet.Cells[1, 1].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));//設定單元格所有邊框
//worksheet.Cells[1, 1].Style.Border.Bottom.Style = ExcelBorderStyle.Thin;//單獨設定單元格底部邊框樣式和顏色(上下左右均可分開設定)
//worksheet.Cells[1, 1].Style.Border.Bottom.Color.SetColor(Color.FromArgb(191, 191, 191));
worksheet.Cells[1, 1].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
worksheet.Cells[1, 2].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
worksheet.Cells[1, 3].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
worksheet.Cells[2, 1].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
worksheet.Cells[2, 2].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
worksheet.Cells[2, 3].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
worksheet.Cells[3, 1].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
worksheet.Cells[3, 2].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
worksheet.Cells[3, 3].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
worksheet.Cells[4, 1].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
worksheet.Cells[4, 2].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
worksheet.Cells[4, 3].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
worksheet.Cells[5, 1].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
worksheet.Cells[5, 2].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
worksheet.Cells[5, 3].Style.Border.BorderAround(ExcelBorderStyle.Thin, Color.FromArgb(191, 191, 191));
#endregion
#region 設定單元格的行高和列寬
worksheet.Cells.Style.ShrinkToFit = true;//單元格自動適應大小
worksheet.Row(1).Height = 15;//設定行高
worksheet.Row(1).CustomHeight = true;//自動調整行高
worksheet.Column(1).Width = 15;//設定列寬
#endregion
#region 設定sheet背景
worksheet.View.ShowGridLines = false;//去掉sheet的網格線
worksheet.Cells.Style.Fill.PatternType = ExcelFillStyle.Solid;
worksheet.Cells.Style.Fill.BackgroundColor.SetColor(Color.LightGray);//設定背景色
//worksheet.BackgroundImage.Image = Image.FromFile(@"C:\Users\cai.j\Pictures\Saved Pictures\8.jpg");//設定背景圖片 沒看出效果
#endregion
#region 插入圖片
ExcelPicture picture = worksheet.Drawings.AddPicture("logo1", Image.FromFile(@"C:\Users\cai.j\Pictures\Saved Pictures\9.jpg"));//插入圖片
picture.SetPosition(100, 100);//設定圖片的位置
picture.SetSize(100, 100);//設定圖片的大小
#endregion
#region 插入形狀 看不出來
//ExcelShape shape = worksheet.Drawings.AddShape("shape", eShapeStyle.Rect);//插入形狀
//shape.Font.Color = Color.Red;//設定形狀的字型顏色
//shape.Font.Size = 15;//字型大小
//shape.Font.Bold = true;//字型粗細
//shape.Fill.Style = eFillStyle.NoFill;//設定形狀的填充樣式
//shape.Border.Fill.Style = eFillStyle.NoFill;//邊框樣式
//shape.SetPosition(200, 300);//形狀的位置
//shape.SetSize(80, 30);//形狀的大小
//shape.Text = "test";//形狀的內容
#endregion
#region 給圖片加超連結 有問題
//picture = worksheet.Drawings.AddPicture("logo2", Image.FromFile(@"C:\Users\cai.j\Pictures\Saved Pictures\5.jpg"), new ExcelHyperLink("http:\\www.baidu.com", UriKind.Relative));
//picture.SetPosition(700, 700);//設定圖片的位置
//picture.SetSize(100, 100);//設定圖片的大小
#endregion
#region 給單元格加超連結
//worksheet.Cells[1, 1].Hyperlink = new ExcelHyperLink("http:\\www.baidu.com", UriKind.Relative);
#endregion
#region 隱藏sheet 有問題
//worksheet.Hidden = eWorkSheetHidden.Hidden;//隱藏sheet
//worksheet.Column(1).Hidden = true;//隱藏某一列
//worksheet.Row(1).Hidden = true;//隱藏某一行
#endregion
#region 建立柱狀圖
ExcelChart chart = worksheet.Drawings.AddChart("chart", eChartType.ColumnClustered);//建立圖表
ExcelChartSerie serie = chart.Series.Add(worksheet.Cells[2, 3, 5, 3], worksheet.Cells[2, 1, 5, 1]);//選擇資料,設定圖表的x軸和y軸
serie.HeaderAddress = worksheet.Cells[1, 3];//設定圖表的圖例
chart.SetPosition(150, 10);//設定位置
chart.SetSize(500, 300);//設定大小
chart.Title.Text = "銷量走勢";//設定圖表的標題
chart.Title.Font.Color = Color.FromArgb(89, 89, 89);//設定標題的顏色
chart.Title.Font.Size = 15;//標題的大小
chart.Title.Font.Bold = true;//標題的粗體
chart.Style = eChartStyle.Style15;//設定圖表的樣式
chart.Legend.Border.LineStyle = eLineStyle.Solid;
chart.Legend.Border.Fill.Color = Color.FromArgb(217, 217, 217);//設定圖例的樣式
#endregion
#region Excel加密和鎖定 沒測過
//worksheet.Protection.IsProtected = true;//設定是否進行鎖定
//worksheet.Protection.SetPassword("yk");//設定密碼
//worksheet.Protection.AllowAutoFilter = false;//下面是一些鎖定時許可權的設定
//worksheet.Protection.AllowDeleteColumns = false;
//worksheet.Protection.AllowDeleteRows = false;
//worksheet.Protection.AllowEditScenarios = false;
//worksheet.Protection.AllowEditObject = false;
//worksheet.Protection.AllowFormatCells = false;
//worksheet.Protection.AllowFormatColumns = false;
//worksheet.Protection.AllowFormatRows = false;
//worksheet.Protection.AllowInsertColumns = false;
//worksheet.Protection.AllowInsertHyperlinks = false;
//worksheet.Protection.AllowInsertRows = false;
//worksheet.Protection.AllowPivotTables = false;
//worksheet.Protection.AllowSelectLockedCells = false;
//worksheet.Protection.AllowSelectUnlockedCells = false;
//worksheet.Protection.AllowSort = false;
#endregion
#region 下拉框
var citys = InitCity();
for (int idx = 0, int count = citys.Count; idx < count; idx++)
{
hideSheet.Cells[idx + 1, 1].Value = citys[idx].Name;
//hideSheet.Cells[idx + 1, 2].Value = citys[idx].ParentId;
}
var val = worksheet.DataValidations.AddListValidation("H1");//設定下拉框顯示的資料區域
val.Formula.ExcelFormula = "=hideSheet!$A:$A";//資料區域的名稱 //資料需要時通過程式碼新增的
val.Prompt = "選擇省份";//下拉提示
val.ShowInputMessage = true;//顯示提示內容
#endregion
package.Save();//儲存Excel
}
return true;
}
private static List<KeyValue> InitCity()
{
var list = new List<KeyValue>
{
new KeyValue {Id = 1, ParentId = 1, Name = "上海市1"},
new KeyValue {Id = 2, ParentId = 2, Name = "徐州市"},
new KeyValue {Id = 3, ParentId = 2, Name = "蘇州市"},
new KeyValue {Id = 4, ParentId = 2, Name = "崑山市"},
new KeyValue {Id = 5, ParentId = 2, Name = "南京市"},
new KeyValue {Id = 6, ParentId = 3, Name = "北京市1"}
};
return list;
}
另外,直接將datatable轉化成Excel的程式碼很簡單,也可以將其他的集合輸出到Excel,像list、dictionary等,需要的自己研究下,我就不貼了。
#region 匯出到Excel ExportByEPPlus(DataTable sourceTable, string strFileName)
/// <summary>
/// 使用EPPlus匯出Excel(xlsx)
/// </summary>
/// <param name="sourceTable">資料來源</param>
/// <param name="strFileName">xlsx檔名(不含字尾名)</param>
public static string ExportByEPPlus(DataTable sourceTable, string strFileName)
{
string FileName = AppDomain.CurrentDomain.BaseDirectory + strFileName + ".xlsx";
if (System.IO.File.Exists(FileName)) //存在則刪除
System.IO.File.Delete(FileName);
ExcelPackage ep = new ExcelPackage();
ExcelWorkbook wb = ep.Workbook;
ExcelWorksheet ws = wb.Worksheets.Add("result");
ws.Cells["A1"].LoadFromDataTable(sourceTable, false);
ep.SaveAs(new FileInfo(FileName));
return FileName;
}
#endregion
最後記錄一下,寫程式碼時考慮到的優化tip:
- 儘可能使用區域性變數
主要是為了記憶體考慮,區域性變數在方法呼叫結束後就over了。另外,就是區域性變數建立的速度更快。
-
減少重複計算
主要是針對迴圈裡面
for (int idx = 0, int count = citys.Count; idx < count; idx++)
{
}
- 迴圈內不要不斷建立物件引用,可以更改引用
IRow row = null;
ICell cell = null;
for (int i = 0, int row_count = dt.Rows.Count; i < row_count; i++)
{
row = sheet.CreateRow(i + 1);
for (int j = 0, int column_count = dt.Columns.Count; j < column_count; j++)
{
cell = row.CreateCell(j);
cell.SetCellValue(dt.Rows[i][j].ToString());
}
}
- 使用帶緩衝的輸入輸出流進行IO操作
- 非必要真的別再catch裡進行流程控制