Reflection-實際運用在NPOI匯出Excel
最近諸事不順,雖然剛過完25歲生日,但熊熊有種很不踏實的感覺。
之前公司有個75年次的同事,小我一歲,不過在程式方面對我來說卻是我憧憬的對象(我不是GAY)
光就技術方面,也許他也只能算中等而已,但光憑他在自己分內的工作的熟悉度以及教人的耐心
程度來看,是我非常想效法的對象。
我的小標題"永遠不要忘記菜的時候",
一方面是指無知就是力量,當你覺得自己懂得越少,才越能吸收更多的東西之外;
另一方面,是因為看過太多有實力的人那副驕傲的嘴臉,活像打從娘胎就會寫Code一樣。
也是在提醒自己,如果將來自己會了一點什麼,也絕對要以菜鳥的心來教別人。
廢話太多,開始今天的正題。Reflection
這東西的說明,請看參考連結
雖然兩個參考連結的範例都是用JAVA,但觀念是不分語言的。
實際Code的寫法,可看黑暗大的這篇 CODE-Reflection範例
看完之後,大概可以了解一些Reflection的用法。
Reflection可在執行時才解析物件類別的資訊,今天就來以NPOI匯出Excel這個例子
來好好學一下Reflection的便利。
以前在用NPOI寫匯出Excel的時候,雖然Code都很簡單,但最麻煩的是標題,跟Cell要一行一行
的加上去。尤其欄位一多,那就不是在寫程式,而是在做苦工了。
例如下面這樣....
for (int i = 0; i < xxx.Length; i++)
{
sheet.CreateRow(0).CreateCell(i).SetCellValue("標題");
sheet.CreateRow(0).CreateCell(i).SetCellValue("名稱");
sheet.CreateRow(0).CreateCell(i).SetCellValue("價格");
sheet.CreateRow(0).CreateCell(i).SetCellValue("數量");
sheet.CreateRow(0).CreateCell(i).SetCellValue("描述");
sheet.CreateRow(0).CreateCell(i).SetCellValue("建立時間");
}
但用Reflection,可讓類別本身去審視自己的欄位、Value或是方法之類的資訊
在一些簡單的需求上,可以很輕鬆地完成我要的效果。
首先還是先拉出一個dbml
這是一個產品的資料表,裡面有13個欄位,
但拉出dbml後會自動產生一些屬性以及方法的class,我就拿這個型別來用
接著下載NPOI,然後把該參考的組件參考進專案
接著用法我就不說明啦,因為官網的說明應該會比我講得清楚,就直接來看Reflection的部分
分兩段寫,首先先設excel第一列的標題
MyDataContext db = new MyDataContext();
//先撈出產品的所有資料
IEnumerable<Products> data = db.Products;
//取得Product的Type
Type t = new Products().GetType();
//利用GetProperties()找出Product的所有屬性
for (int i = 0; i < t.GetProperties().Length; i++)
{
sheet.SetColumnWidth(i, 20 * 256);
//從第0個Cell開始,把屬性的名稱設進去。
sheet.CreateRow(0).CreateCell(i).SetCellValue(t.GetProperties()[i].Name);
}
因為我的Table原本的欄位名稱就還算清楚,所以就直接拿來當標題名稱
接著用迴圈,把剛剛從資料庫抓出來的所有資料放進去。
int count = 1;
foreach (Products d in data)
{
for (int i = 0; i < t.GetProperties().Length; i++)
{
sheet.CreateRow(count).CreateCell(i).SetCellValue(Convert.ToString(t.GetProperties()[i].GetValue(d, null)));
}
count++;
}
跑兩層迴圈,外面那個是一列一列的資料,裡面那層是每一列的各個Cell的資料。
(小提醒:在不確定有沒有資料時,用Convert.ToString()才不會發生例外)
接著貼完整的Code
public ActionResult 匯出Excel()
{
//前面這幾段Code在NPOI的官網都可以找的到
HSSFWorkbook workbook = new HSSFWorkbook();
MemoryStream ms = new MemoryStream();
HSSFSheet sheet = workbook.CreateSheet("試算表");
MyDataContext db = new MyDataContext();
//先撈出產品的所有資料
IEnumerable<Products> data = db.Products;
//取得Product的Type
Type t = new Products().GetType();
//利用GetProperties()找出Product的所有屬性
for (int i = 0; i < t.GetProperties().Length; i++)
{
sheet.SetColumnWidth(i, 20 * 256);
//從第0個Cell開始,把屬性的名稱設進去。
sheet.CreateRow(0).CreateCell(i).SetCellValue(t.GetProperties()[i].Name);
}
int count = 1;
foreach (Products d in data)
{
for (int i = 0; i < t.GetProperties().Length; i++)
{
sheet.CreateRow(count).CreateCell(i).SetCellValue(Convert.ToString(t.GetProperties()[i].GetValue(d, null)));
}
count++;
}
workbook.Write(ms);
workbook = null;
return File(ms.ToArray(), "application/vnd.ms-excel", "report.xls");
}
基本上Code都滿簡單,也很直覺。一個就是GetProperty,另一個就是GetValue,其餘的都是NPOI的工作
然後用MVC的return File(),告訴瀏覽器這是xls檔,然後給個檔名,執行看看結果如何
短短的幾行Code,就解決基本的要求啦。當然如果要客制化欄位,就要再加一些判斷了。
前面的範例我是拿我已知的型別,去抓他的屬性跟值。也可改寫成一開始不知道是甚麼型別
用泛型加上Reflection
public ActionResult Action()
{
MyDataContext db = new MyDataContext();
IEnumerable<Expenses> data = db.Expenses;
MemoryStream ms = 匯出Excel<IEnumerable<Expenses>, Expenses>(data, new Expenses());
//另外一個型別的資料,丟什麼型別進去就可依型別吐出不同東西
//IEnumerable<Products> data = db.Products;
//MemoryStream ms = 匯出Excel<IEnumerable<Products>, Products>(data, new Products());
return File(ms.ToArray(), "application/vnd.ms-excel", "report.xls");
}
public static MemoryStream 匯出Excel<T1,T2>(T1 obj1,T2 obj2)
{
//前面這幾段Code在NPOI的官網都可以找的到
HSSFWorkbook workbook = new HSSFWorkbook();
MemoryStream ms = new MemoryStream();
HSSFSheet sheet = workbook.CreateSheet("試算表");
//先撈出所有資料
IEnumerable<T2> data = (IEnumerable<T2>)obj1;
//取得T2的Type
Type t = obj2.GetType();
//利用GetProperties()找出T2的所有屬性
for (int i = 0; i < t.GetProperties().Length; i++)
{
sheet.SetColumnWidth(i, 20 * 256);
//從第0個Cell開始,把屬性的名稱設進去。
sheet.CreateRow(0).CreateCell(i).SetCellValue(t.GetProperties()[i].Name);
}
int count = 1;
foreach (T2 d in data)
{
for (int i = 0; i < t.GetProperties().Length; i++)
{
sheet.CreateRow(count).CreateCell(i).SetCellValue(Convert.ToString(t.GetProperties()[i].GetValue(d, null)));
}
count++;
}
workbook.Write(ms);
workbook = null;
return ms;
}
改成這樣,就可在Action中,丟不同型別進去。然後動態撈出該型別的屬性及值匯出。
呼,大概是這樣,不知道寫得好不好。
由於我是半路出家,所以在這行除了同事之外,幾乎沒有其他朋友。
但希望我寫的東西,可以讓跟我一樣是新手的人一起長大。
學會自己想學的,做自己想做的,分享自己所得的,是我今年度的目標。
至於誰答腔我就說誰,諸如此類的話...我就不再理會了。
-----------------------2010-07-1 經高人指點修改後的Code-------------------------------
由於上面那個泛型的範例寫得有點爛,今天問同事之後,可以寫得更簡單一點
//改成只用一個泛型 public static MemoryStream 匯出Excel<T>(IEnumerable<T> obj1,T obj2) { //前面這幾段Code在NPOI的官網都可以找的到 HSSFWorkbook workbook = new HSSFWorkbook(); MemoryStream ms = new MemoryStream(); HSSFSheet sheet = workbook.CreateSheet("試算表"); //取得T2的Type Type t = obj2.GetType(); //利用GetProperties()找出T2的所有屬性 for (int i = 0; i < t.GetProperties().Length; i++) { sheet.SetColumnWidth(i, 20 * 256); //從第0個Cell開始,把屬性的名稱設進去。 sheet.CreateRow(0).CreateCell(i) .SetCellValue(t.GetProperties()[i].Name); } int count = 1; foreach (T d in obj1) { for (int i = 0; i < t.GetProperties().Length; i++) { sheet.CreateRow(count).CreateCell(i) .SetCellValue(Convert.ToString(t.GetProperties()[i].GetValue(d, null))); } count++; } workbook.Write(ms); workbook = null; return ms; }
使用時變這樣
IEnumerable<Products> data = db.Products;
MemoryStream ms = 匯出Excel<Products>(data, new Products());