前言:
相信大家都玩過NPOI這個第三方元件,我就分享一下我平時使用的工具類,如果有不好的地方,請賜教!
NPOI是什麼?
NPOI是一個開源的C#讀寫Excel、WORD等微軟OLE2元件文件的專案。
NPOI怎麼安裝?
NuGet:
控制檯:
命令:
Install-Package NPOI
輸入命令之後,回車即安裝
NPOI怎麼使用?
安裝NPOI之後,程式中就已經把NPOI服務整合到我們程式了,我們現在來建立一個幫助類,編寫讀取Execl和匯出Execl。我這裡的讀取Execl,把每一個Sheet頁當做一個DataTable,多個DataTable組成一個DataSet,然後將DataSet返回。
NPOI讀取Execl
/// <summary>
/// Excel匯入成DataTble
/// </summary>
/// <param name="file">匯入路徑(包含檔名與副檔名)</param>
/// <returns></returns>
public static DataSet ExcelToTable(string file, ref List<string> list_sheetName)
{
DataSet ds = new DataSet();
IWorkbook workbook;
string fileExt = Path.GetExtension(file).ToLower();
using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
{
if (fileExt == ".xlsx") { workbook = new XSSFWorkbook(fs); } else if (fileExt == ".xls") { workbook = new HSSFWorkbook(fs); } else { workbook = null; }
if (workbook == null) { return null; }
for (int k = 0; k < workbook.NumberOfSheets; k++)
{
DataTable dt = new DataTable();
ISheet sheet = workbook.GetSheetAt(k);
list_sheetName.Add(sheet.SheetName);
//表頭
IRow header = sheet.GetRow(sheet.FirstRowNum);
//過濾空的Sheet
if (header!=null)
{
List<int> columns = new List<int>();
for (int i = 0; i < header.LastCellNum; i++)
{
object obj = GetValueType(header.GetCell(i));
if (obj == null || obj.ToString() == string.Empty)
{
dt.Columns.Add(new DataColumn("Columns" + i.ToString()));
}
else
dt.Columns.Add(new DataColumn(obj.ToString()));
columns.Add(i);
}
dt.Columns.Add(new DataColumn("SheetName"));
//資料
for (int i = sheet.FirstRowNum + 1; i <= sheet.LastRowNum; i++)
{
DataRow dr = dt.NewRow();
bool hasValue = false;
foreach (int j in columns)
{
if (sheet.GetRow(i) != null)
{
dr[j] = GetValueType(sheet.GetRow(i).GetCell(j));
if (dr[j] != null && dr[j].ToString() != string.Empty)
{
hasValue = true;
}
}
}
if (hasValue)
{
dr[columns.Count] = sheet.SheetName;
dt.Rows.Add(dr);
}
}
ds.Tables.Add(dt);
}
}
}
return ds;
}
/// <summary>
/// 獲取單元格型別
/// </summary>
/// <param name="cell">目標單元格</param>
/// <returns></returns>
private static object GetValueType(ICell cell)
{
if (cell == null)
return null;
switch (cell.CellType)
{
case CellType.Blank:
return null;
case CellType.Boolean:
return cell.BooleanCellValue;
case CellType.Numeric:
return cell.NumericCellValue;
case CellType.String:
return cell.StringCellValue;
case CellType.Error:
return cell.ErrorCellValue;
case CellType.Formula:
default:
return "=" + cell.CellFormula;
}
}
思考?
我這裡讀取之後是一個DataSet集合,但是這種資料集雖然在結構上很清晰,一個DataTable對應一個Sheet,但是處理器資料其他麻煩(比如,我想查詢表中Name為"張三"的使用者資訊,肯定是不好查詢的),還是就是如果在每個Sheet資料格式相同的情況下,肯定會有想把它們整合在一起的想法,那該如何整合在一起?
思路:
要是能轉換為List陣列就好,我們就能使用Linq和Lambda進行資料的快速處理。如何把DataSet轉換為List
,我們可以觀察execl中的資料,然後對應在專案中建立一個Model類,用英文做欄位,用DisplayName標識對應的中文(為什麼要這樣,後面會講),建立一個List ,現在只要把DataSet中的DataTable取出來,然後利用反射的方式,比較DataTable中的列名和Model中對應的DisplayName,如何一樣,,則儲存到List ,這裡存在一個DataTable轉換為List 。
Execl資料:
Mode類:
只要這樣一一對應起來,後期委會也好維護,比如新增了一個列,在Model中加一個欄位即可,方便擴充套件,如果是多個Sheet,每個Sheet略有不同,就可以使用c#物件導向的思想,提取它們共同的欄位,其他做繼承,這裡就不多說了。接下來講講如何做DataSet轉換為List,其實DataSet中就是好多DataTable組成,如果能實現DataTable轉換到List ,其他就迎刃而解!
DataTable轉換List
public static class DataTableToList
{
/// <summary>
/// DataTable轉成List
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dt"></param>
/// <returns></returns>
public static List<T> ToDataList<T>(this DataTable dt)
{
var list = new List<T>();
var plist = new List<PropertyInfo>(typeof(T).GetProperties());
foreach (DataRow item in dt.Rows)
{
T s = Activator.CreateInstance<T>();
for (int i = 0; i < dt.Columns.Count; i++)
{
PropertyInfo info = plist.Find(p => p.GetCustomAttribute<System.ComponentModel.DisplayNameAttribute>().DisplayName == dt.Columns[i].ColumnName);
if (info != null)
{
try
{
if (!Convert.IsDBNull(item[i]))
{
object v = null;
if (info.PropertyType.ToString().Contains("System.Nullable"))
{
v = Convert.ChangeType(item[i], Nullable.GetUnderlyingType(info.PropertyType));
}
else
{
v = Convert.ChangeType(item[i], info.PropertyType);
}
info.SetValue(s, v, null);
}
}
catch (Exception ex)
{
throw new Exception("欄位[" + info.Name + "]轉換出錯," + ex.Message);
}
}
}
list.Add(s);
}
return list;
}
public static List<T> ToDataSetList<T>(this DataSet ds)
{
var list = new List<T>();
for (int i = 0; i < ds.Tables.Count; i++)
{
list = list.Concat(ToDataList<T>(ds.Tables[i])).ToList();
}
return list;
}
}
這裡是使用泛型+反射的技術,對DataTable轉換為List
進行封裝,只要你的格式一致(Execl和Model),就可以實現轉換。
匯出Execl
可以匯入Execl,然後轉換為List之後,我們可以為所欲為了,但是修改資料以後,我們可以想儲存資訊到Execl。
思考?
- 如何匯出List
到一個新的Execl?
思路:
- List
這個思路很簡單,第一步建立一個IWorkbook(Execl物件),第二部建立Sheet,起個名字,然後把List資料遍歷到Sheet中,最後寫入到檔案中。
/// <summary>
/// List<T>匯出Execl
/// </summary>
/// <typeparam name="T">模型類</typeparam>
/// <param name="file">儲存檔案的路徑</param>
/// <param name="list">需要儲存的資料</param>
public static void ListToExecl<T>(string file, List<T> list)
{
IWorkbook workbook;
string fileExt = Path.GetExtension(file).ToLower();
if (fileExt == ".xlsx") { workbook = new XSSFWorkbook(); } else if (fileExt == ".xls") { workbook = new HSSFWorkbook(); } else { workbook = null; }
if (workbook == null) { return; }
//中文顯示的列名
string DisplayName = string.Empty;
ISheet sheet = workbook.CreateSheet();
//表頭
IRow header = sheet.CreateRow(0);
Type t = typeof(T);
PropertyInfo[] properties = t.GetProperties();
int index = 0;
foreach (PropertyInfo field in properties)
{
DynamicGetProperty(list[0], field.Name, ref DisplayName).ToString();
header.CreateCell(index).SetCellValue(DisplayName);
index += 1;
}
for (int i = 0; i < list.Count; i++)
{
index = 0;
header = sheet.CreateRow(1+i);
foreach (PropertyInfo field in properties)
{
string name = DynamicGetProperty(list[i], field.Name, ref DisplayName).ToString();
header.CreateCell(index).SetCellValue(name);
index += 1;
}
}
using (FileStream fs=new FileStream(file,FileMode.Create,FileAccess.ReadWrite))
{
workbook.Write(fs);
}
}
/// <summary>
/// 動態獲取物件的屬性
/// </summary>
/// <param name="obj">傳入的物件</param>
/// <param name="propName">屬性名</param>
/// <returns></returns>
public static object DynamicGetProperty(object obj, string propName,ref string DisplayName)
{
// TODO: 檢查屬性名合法性
var propNames = propName.Split('.');
var val = obj;
foreach (var prop in propNames)
{
var propInfo = val.GetType().GetProperty(prop);
DisplayName = propInfo.GetCustomAttribute<DisplayNameAttribute>().DisplayName;
val = propInfo.GetValue(val);
}
return val;
}
總結:
我在這只是拋磚引玉,其實NPOI還有一些其他東西, 大家可以自行研究,比如:DataTable匯出Exelc,C# NPOI計算Execl裡面的公式等等。
- 肯定有人會有疑問,我為什麼要把Execl先轉換為DataSet,在去轉換為List
,為什麼不在一開始就去轉換為List ? - 答:第一我們不知道Execl資料的有多少Sheet,如果針對每一個都去寫一個規則,繁瑣且麻煩。你按照我的這種方式,不管你有多少Sheet,只要我知道你的格式【列名】,我就都可以轉換為List
,雖然多了一層轉換,但是我邏輯清晰,程式碼複用率高,針對不同的Sheet編寫對應的模型就可以,並不需要我每次去編寫特定的格式。 - 有人還是有疑問,你這匯出怎麼就List
直接轉換到一個Sheet中,如果我想分開,之前怎麼讀取,之後就怎麼儲存,我該如何做? - 答:其實這個也挺簡單,我沒做擴充套件,你在使用我程式碼的時候,一定會發現,List
中多了一列值【SheetName】,所以你在儲存的時候,讀SheetName進行分類,然後遍歷儲存即可。
原文地址:https://www.cnblogs.com/2828sea/p/13493710.html