C# Net 使用 openxml 讀取 Excel
C# Net 使用 openxml 讀取Excel到對象
C# Net Core 使用 openxml 讀取Excel
C# Net Core 使用 openxml 讀取Excel到對象
注:需要寫入對象到Excel請參考另一篇博客(https://www.cnblogs.com/ping9719/p/12539737.html)
------------------------------------------------------------
------------------------------------------------------------
-------------------------文尾看效果---------------------
------------------------------------------------------------
------------------------------------------------------------
加入包:OpenXml
創建文件:ExcelRead.cs
復制下面全部代碼到文件 ExcelRead.cs
using System; using System.Collections.Generic; using System.IO; using System.Linq; using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Office2010.ExcelAc; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Spreadsheet; using YGNT.Office.ExcelXml.Models; namespace YGNT.Office.ExcelXml { /// <summary> /// 讀取Excel /// </summary> public class ExcelRead { /// <summary> /// 讀取、解析 /// </summary> /// <param name="fileName">文件</param> /// <param name="sheetName">工作表(默認第一個)</param> /// <param name="type">1 不去空格 2 前后空格 3 所有空格 </param> /// <returns></returns> public static List<ExcelCellInfo> Read(string fileName, string sheetName = "", int type = 2) { List<ExcelCellInfo> excelCellInfos = new List<ExcelCellInfo>(); using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(fileName, false)) { WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart; //查找工作薄 Sheet sheet = ExcelSeek.SeekSheet(workbookPart, sheetName); //工作表 WorksheetPart worksheetPart = (WorksheetPart)spreadsheetDocument.WorkbookPart.GetPartById(sheet.Id); //數據行 var rows = worksheetPart.Worksheet.Descendants<Row>();//獲得Excel中得數據行 foreach (Row r in rows) { foreach (Cell c in r.Elements<Cell>()) { ExcelCellInfo excelCellInfo = new ExcelCellInfo(); excelCellInfo.RowIndex = (int)r.RowIndex.Value; excelCellInfo.CellReference = c.CellReference; excelCellInfo.ColumnIndex = ExcelAlphabet.ABCToColumn(excelCellInfo.CellReference.Replace(excelCellInfo.RowIndex.ToString(), "")); excelCellInfo.Value = GetCellValue(c, workbookPart, type); excelCellInfos.Add(excelCellInfo); } } } return excelCellInfos; } /// <summary> /// 讀取、解析 /// </summary> /// <param name="fileName">文件</param> /// <param name="sheetName">工作表(默認第一個)</param> /// <param name="type">1 不去空格 2 前后空格 3 所有空格 </param> /// <returns></returns> public static List<T> Read<T>(string fileName, string sheetName = "", int type = 2) where T : new() { List<ExcelCellInfo> excelCellInfos = Read(fileName, sheetName, type); List<T> t = new List<T>(); //所有屬性 var properties = new T().GetType().GetProperties(); //exc中第一行單元格 var oneRow = excelCellInfos.Where(o => o.RowIndex == 1 && !string.IsNullOrEmpty(o.Value)); //屬性和單元格關系(key:屬性,val:單元格) var p_OneROw = new Dictionary<System.Reflection.PropertyInfo, ExcelCellInfo>(); //給【p_OneROw】賦值 foreach (var property in properties) { //取屬性上的自定義特性 ExcelColumnAttribute att = null; var atts = (IEnumerable<ExcelColumnAttribute>)property.GetCustomAttributes(typeof(ExcelColumnAttribute), false); if (atts.Any()) att = atts.First(); if (att != null && att.IsShow) { string eName = att.ColumnName; var lie = oneRow.FirstOrDefault(o => o.Value == eName); if (lie != null) { p_OneROw.Add(property, lie); } } else { string eName = property.Name; var lie = oneRow.FirstOrDefault(o => o.Value == eName); if (lie != null) { p_OneROw.Add(property, lie); } } } for (int i = 2; i <= excelCellInfos.Max(o => o.RowIndex); i++) { var model = new T(); foreach (var por in p_OneROw) { var clee = excelCellInfos.FirstOrDefault(o => o.RowIndex == i && o.ColumnIndex == por.Value.ColumnIndex); if (clee != null) { string ty = por.Key.PropertyType.FullName; if (ty.Contains("System.String")) por.Key.SetValue(model, clee.Value); else if (ty.Contains("System.DateTime")) por.Key.SetValue(model, Convert.ToDateTime(clee.Value)); else if (ty.Contains("System.Single")) por.Key.SetValue(model, Convert.ToSingle(clee.Value)); else if (ty.Contains("System.Boolean")) por.Key.SetValue(model, Convert.ToBoolean(clee.Value)); else if (ty.Contains("System.Byte")) por.Key.SetValue(model, Convert.ToByte(clee.Value)); else if (ty.Contains("System.Int16")) por.Key.SetValue(model, Convert.ToInt16(clee.Value)); else if (ty.Contains("System.Int32")) por.Key.SetValue(model, Convert.ToInt32(clee.Value)); else if (ty.Contains("System.Int64")) por.Key.SetValue(model, Convert.ToInt64(clee.Value)); else if (ty.Contains("System.Double")) por.Key.SetValue(model, Convert.ToDouble(clee.Value)); else if (ty.Contains("System.Decimal")) por.Key.SetValue(model, Convert.ToDecimal(clee.Value)); } } t.Add(model); } return t; } /// <summary> /// 讀取、解析 /// </summary> /// <param name="stream">文件</param> /// <param name="sheetName">工作表(默認第一個)</param> /// <param name="type">1 不去空格 2 前后空格 3 所有空格 </param> /// <returns></returns> public static List<ExcelCellInfo> Read(Stream stream, string sheetName = "", int type = 2) { List<ExcelCellInfo> excelCellInfos = new List<ExcelCellInfo>(); using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(stream, false)) { WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart; //查找工作薄 Sheet sheet = ExcelSeek.SeekSheet(workbookPart, sheetName); //工作表 WorksheetPart worksheetPart = (WorksheetPart)spreadsheetDocument.WorkbookPart.GetPartById(sheet.Id); //數據行 var rows = worksheetPart.Worksheet.Descendants<Row>();//獲得Excel中得數據行 foreach (Row r in rows) { foreach (Cell c in r.Elements<Cell>()) { ExcelCellInfo excelCellInfo = new ExcelCellInfo(); excelCellInfo.RowIndex = (int)r.RowIndex.Value; excelCellInfo.CellReference = c.CellReference; excelCellInfo.ColumnIndex = ExcelAlphabet.ABCToColumn(excelCellInfo.CellReference.Replace(excelCellInfo.RowIndex.ToString(), "")); excelCellInfo.Value = GetCellValue(c, workbookPart, type); excelCellInfos.Add(excelCellInfo); } } } return excelCellInfos; } /// <summary> /// 讀取、解析 /// </summary> /// <param name="stream">文件</param> /// <param name="sheetName">工作表(默認第一個)</param> /// <param name="type">1 不去空格 2 前后空格 3 所有空格 </param> /// <returns></returns> public static List<T> Read<T>(Stream stream, string sheetName = "", int type = 2) where T : new() { List<ExcelCellInfo> excelCellInfos = Read(stream, sheetName, type); List<T> t = new List<T>(); //所有屬性 var properties = new T().GetType().GetProperties(); //exc中第一行單元格 var oneRow = excelCellInfos.Where(o => o.RowIndex == 1 && !string.IsNullOrEmpty(o.Value)); //屬性和單元格關系(key:屬性,val:單元格) var p_OneROw = new Dictionary<System.Reflection.PropertyInfo, ExcelCellInfo>(); //給【p_OneROw】賦值 foreach (var property in properties) { //取屬性上的自定義特性 ExcelColumnAttribute att = null; var atts = (IEnumerable<ExcelColumnAttribute>)property.GetCustomAttributes(typeof(ExcelColumnAttribute), false); if (atts.Any()) att = atts.First(); if (att != null && att.IsShow) { string eName = att.ColumnName; var lie = oneRow.FirstOrDefault(o => o.Value == eName); if (lie != null) { p_OneROw.Add(property, lie); } } else { string eName = property.Name; var lie = oneRow.FirstOrDefault(o => o.Value == eName); if (lie != null) { p_OneROw.Add(property, lie); } } } for (int i = 2; i <= excelCellInfos.Max(o => o.RowIndex); i++) { var model = new T(); foreach (var por in p_OneROw) { var clee = excelCellInfos.FirstOrDefault(o => o.RowIndex == i && o.ColumnIndex == por.Value.ColumnIndex); if (clee != null) { por.Key.SetValue(model, clee.Value); } } t.Add(model); } return t; } /// <summary> /// 獲取單位格的值 /// </summary> /// <param name="cell">單元格</param> /// <param name="workbookPart"></param> /// <param name="type">1 不去空格 2 前后空格 3 所有空格 </param> /// <returns></returns> public static string GetCellValue(Cell cell, WorkbookPart workbookPart, int type = 2) { //合並單元格不做處理 if (cell.CellValue == null) return string.Empty; string cellInnerText = cell.CellValue.InnerXml; //純字符串 if (cell.DataType != null && (cell.DataType.Value == CellValues.SharedString || cell.DataType.Value == CellValues.String || cell.DataType.Value == CellValues.Number)) { //獲取spreadsheetDocument中共享的數據 SharedStringTable stringTable = workbookPart.SharedStringTablePart.SharedStringTable; //如果共享字符串表丟失,則說明出了問題。 if (!stringTable.Any()) return string.Empty; string text = stringTable.ElementAt(int.Parse(cellInnerText)).InnerText; if (type == 2) return text.Trim(); else if (type == 3) return text.Replace(" ", ""); else return text; } //bool類型 else if (cell.DataType != null && cell.DataType.Value == CellValues.Boolean) { return (cellInnerText != "0").ToString().ToUpper(); } //數字格式代碼(numFmtId)小於164是內置的:https://www.it1352.com/736329.html else { //為空為數值 if (cell.StyleIndex == null) return cellInnerText; Stylesheet styleSheet = workbookPart.WorkbookStylesPart.Stylesheet; CellFormat cellFormat = (CellFormat)styleSheet.CellFormats.ChildElements[(int)cell.StyleIndex.Value]; uint formatId = cellFormat.NumberFormatId.Value; double doubleTime;//OLE 自動化日期值 DateTime dateTime;//yyyy/MM/dd HH:mm:ss switch (formatId) { case 0://常規 return cellInnerText; case 9://百分比【0%】 case 10://百分比【0.00%】 case 11://科學計數【1.00E+02】 case 12://分數【1/2】 return cellInnerText; case 14: doubleTime = double.Parse(cellInnerText); dateTime = DateTime.FromOADate(doubleTime); return dateTime.ToString("yyyy/MM/dd"); //case 15: //case 16: case 17: doubleTime = double.Parse(cellInnerText); dateTime = DateTime.FromOADate(doubleTime); return dateTime.ToString("yyyy/MM"); //case 18: //case 19: case 20: doubleTime = double.Parse(cellInnerText); dateTime = DateTime.FromOADate(doubleTime); return dateTime.ToString("H:mm"); case 21: doubleTime = double.Parse(cellInnerText); dateTime = DateTime.FromOADate(doubleTime); return dateTime.ToString("HH:mm:ss"); case 22: doubleTime = double.Parse(cellInnerText); dateTime = DateTime.FromOADate(doubleTime); return dateTime.ToString("yyyy/MM/dd HH:mm"); //case 45: //case 46: case 47: doubleTime = double.Parse(cellInnerText); dateTime = DateTime.FromOADate(doubleTime); return dateTime.ToString("yyyy/MM/dd"); case 58://【中國】11月11日 doubleTime = double.Parse(cellInnerText); dateTime = DateTime.FromOADate(doubleTime); return dateTime.ToString("MM/dd"); case 176://【中國】2020年11月11日 doubleTime = double.Parse(cellInnerText); dateTime = DateTime.FromOADate(doubleTime); return dateTime.ToString("yyyy/MM/dd"); case 177://【中國】11:22:00 doubleTime = double.Parse(cellInnerText); dateTime = DateTime.FromOADate(doubleTime); return dateTime.ToString("HH:mm:ss"); default: return cellInnerText; } } } } }
創建文件:ExcelCellInfo.cs
復制下面全部代碼到文件 ExcelCellInfo.cs
/// <summary> /// 單元格信息 /// </summary> public class ExcelCellInfo { /// <summary> /// 行號,最小1 /// </summary> public int RowIndex { get; set; } /// <summary> /// 列號,最小1 /// </summary> public int ColumnIndex { get; set; } /// <summary> /// 單元格地址,如A1 /// </summary> public string CellReference { get; set; } /// <summary> /// 單元格值 /// </summary> public string Value { get; set; } }
創建文件:ExcelAlphabet.cs
復制下面全部代碼到文件 ExcelAlphabet.cs
using DocumentFormat.OpenXml.Spreadsheet; using System; using System.Collections.Generic; using System.Text; namespace YGNT.Office.ExcelXml { /// <summary> /// Excel字母碼幫助(26進制轉換) /// </summary> public class ExcelAlphabet { //備注 A 對應char為65,Z 對應char為90 /// <summary> /// 26個字母 /// </summary> public static uint AlphabetCount = 26; /// <summary> /// 數字轉字符 /// </summary> /// <param name="iNumber"></param> /// <returns></returns> public static string ColumnToABC(int iNumber) { if (iNumber < 1 || iNumber > 702) throw new Exception("轉為26進制可用10進制范圍為1-702"); string sLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; int iUnits = 26; int iDivisor = (int)(iNumber / iUnits); int iResidue = iNumber % iUnits; if (iDivisor == 1 && iResidue == 0) { iDivisor = 0; iResidue = iResidue + iUnits; } else { if (iResidue == 0) { iDivisor -= 1; iResidue += iUnits; } } if (iDivisor == 0) { return sLetters.Substring(iResidue - 1, 1); } else { return sLetters.Substring(iDivisor - 1, 1) + sLetters.Substring(iResidue - 1, 1); } } /// <summary> /// 字符轉數字 /// </summary> /// <param name="sString"></param> /// <returns></returns> public static int ABCToColumn(string sString) { if (string.Compare(sString, "A") == -1 || string.Compare(sString, "ZZ") == 1) return 0; string sLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; int iUnits = 26; int sFirst = -1; int sSecond = 0; if (sString.Length == 1) { sSecond = sLetters.IndexOf(sString); } else { sFirst = sLetters.IndexOf(sString.Substring(0, 1)); sSecond = sLetters.IndexOf(sString.Substring(1, 1)); } return (sFirst + 1) * iUnits + (sSecond + 1); } } }
創建文件:ExcelColumnAttribute.cs
復制下面全部代碼到文件 ExcelColumnAttribute.cs
using System; using System.Collections.Generic; using System.ComponentModel; using System.Text; namespace YGNT.Office.ExcelXml { /// <summary> /// Excel列特性 /// </summary> public class ExcelColumnAttribute : Attribute //: DescriptionAttribute { /// <summary> /// 建議列名 /// </summary> public virtual string ColumnName { get; } /// <summary> /// 是否顯示列 /// </summary> public virtual bool IsShow { get; } /// <summary> /// 初始化Excel列名的特性 /// </summary> /// <param name="isShow">是否顯示列(在類上為false時不解析默認第一行,在屬性上為false時不顯示屬性的值)</param> public ExcelColumnAttribute(bool isShow = true) { IsShow = isShow; } /// <summary> /// 初始化Excel列名的特性 /// </summary> /// <param name="description">建議列名(在屬性上為Excel中的第一行的頭值)</param> /// <param name="isShow">是否顯示列(在類上為false時不解析默認第一行,在屬性上為false時不顯示屬性的值)</param> public ExcelColumnAttribute(string description, bool isShow = true) { ColumnName = description; IsShow = isShow; } } }
創建文件:ExcelSeek.cs
復制下面全部代碼到文件 ExcelSeek.cs
using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Spreadsheet; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace YGNT.Office.ExcelXml { public class ExcelSeek { /// <summary> /// 在工作薄中查找工作表 /// </summary> public static Sheet SeekSheet(WorkbookPart workbookPart, string sheetName = "") { //獲取所有工作薄 IEnumerable<Sheet> sheets = workbookPart.Workbook.Descendants<Sheet>(); Sheet sheet = null; if (!sheets.Any()) throw new ArgumentException("空的Excel文檔"); if (string.IsNullOrEmpty(sheetName)) sheet = sheets.First(); else { if (sheets.Count(o => o.Name == sheetName) <= 0) throw new ArgumentException($"沒有找到工作薄“{sheetName}”"); sheet = sheets.First(o => o.Name == sheetName); } return sheet; } /// <summary> /// 根據工作表獲取工作頁 /// </summary> /// <param name="sheet">工作表</param> /// <returns>工作頁</returns> public static WorksheetPart GetWorksheetPart(WorkbookPart workbookPart, Sheet sheet) { return (WorksheetPart)workbookPart.GetPartById(sheet.Id); } } }
--------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------
-------------開始調用(讀取文件信息到對象集合)----------------------------
--------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------
創建一個模型
using System.ComponentModel.DataAnnotations; using YGNT.Office.ExcelXml; namespace YGNT.Model.Student { public class StudentExcelDto { /// <summary> /// 班級名稱 /// </summary> [ExcelColumn("班級名稱(必填)")] public string ClassName { get; set; } /// <summary> /// 學員姓名 /// </summary> [ExcelColumn("學員姓名(必填)")] public string StudentName { get; set; } /// <summary> /// 手機號碼 /// </summary> [ExcelColumn("手機號碼")] public string Mobile { get; set; }
//省略其他信息........... } }
估計另一篇博文,可以根據模型生成模板Excel文件(給用戶),這里也可以自己准備模板文件
var path = ExcelCreate.NewCreate(); ExcelWrite.WriteObj(path, new List<StudentExcelDto>());
自動生成的模板文件:
在上面的文檔中填入信息。。。
獲取文檔中的信息
var data = ExcelRead.Read<StudentExcelDto>("學員導入模板.xlsx");
效果為: