基礎類庫積累--ExeclHelper類

喜歡吃魚的青年發表於2020-08-13

前言:

相信大家都玩過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

相關文章