Reflection-實際運用在NPOI匯出Excel(7/1修改)

Reflection-實際運用在NPOI匯出Excel

最近諸事不順,雖然剛過完25歲生日,但熊熊有種很不踏實的感覺。

之前公司有個75年次的同事,小我一歲,不過在程式方面對我來說卻是我憧憬的對象(我不是GAY)

光就技術方面,也許他也只能算中等而已,但光憑他在自己分內的工作的熟悉度以及教人的耐心

程度來看,是我非常想效法的對象。

 

我的小標題"永遠不要忘記菜的時候",

一方面是指無知就是力量,當你覺得自己懂得越少,才越能吸收更多的東西之外;

另一方面,是因為看過太多有實力的人那副驕傲的嘴臉,活像打從娘胎就會寫Code一樣。

也是在提醒自己,如果將來自己會了一點什麼,也絕對要以菜鳥的心來教別人。

 

廢話太多,開始今天的正題。Reflection

這東西的說明,請看參考連結

什麼是 Reflection ?

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

image

這是一個產品的資料表,裡面有13個欄位,

但拉出dbml後會自動產生一些屬性以及方法的class,我就拿這個型別來用

接著下載NPOI,然後把該參考的組件參考進專案

image

接著用法我就不說明啦,因為官網的說明應該會比我講得清楚,就直接來看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檔,然後給個檔名,執行看看結果如何

image

image

 

image

短短的幾行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());