using NPOI.HSSF.UserModel; using NPOI.SS.Formula.Functions; using NPOI.SS.UserModel; using NPOI.XSSF.UserModel; using System; using System.Collections.Generic; using System.Data.Common; using System.IO; using System.Linq; using System.Web; using WebLib; namespace Education { public class ImportBase { protected WebLib.Excel excel; protected List columns; private static string importFilePath = "~/DocUpload/Import"; protected DbConnection connection; protected static List trueValues = new List() { "Y", "V", "T", "1", "YES", "TRUE" }; public const double MinOADate = 43831.0; // 2020/01/01 public const double MaxOADate = 2958465.0; // 9999/12/31 public ImportBase() { excel = new WebLib.Excel(); columns = new List(); } public DbConnection Connection { get { return connection; } set { connection = value; } } public static string ImportFilePath { get { var fullPath = HttpContext.Current.Server.MapPath(importFilePath); if (!Directory.Exists(fullPath)) { Directory.CreateDirectory(fullPath); } return fullPath; } } public MemoryStream ImportExample() { var workbook = new XSSFWorkbook(); var sheet = workbook.CreateSheet("匯入範例"); excel.Sheet = sheet; int col = 0; foreach (var column in columns) { excel.SetCellValue(0, col, column.Title); excel.SetColumnWidthInChars(col, column.Width); col++; } MemoryStream ms = new MemoryStream(); workbook.Write(ms); return ms; } public IWorkbook LoadWorkbook(string filePath) { using (FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { if (Path.GetExtension(filePath).ToLower() == ".xls") { return new HSSFWorkbook(file); } else if (Path.GetExtension(filePath).ToLower() == ".xlsx") { return new XSSFWorkbook(file); } else { throw new Exception("不支援的檔案格式"); } } } public List Validate(string filePath, Type type, out int rowCount) { // 1. 開啟 Excel 檔案 var workbook = LoadWorkbook(filePath); // 2. 讀取第一個工作表 var sheet = workbook.GetSheetAt(0); // 3. 驗證欄位名稱 var headerRow = sheet.GetRow(0); var errors = ValidateColumns(headerRow, out var columnMap); // 4. 驗證每一列資料 rowCount = 0; for (int rowId = 1; rowId <= sheet.LastRowNum; rowId++) { var row = sheet.GetRow(rowId); if (row == null) { break; } var rowErrors = ValidateRow(row, type, columnMap, out bool stop); foreach (var err in rowErrors) { errors.Add($"第 {rowId} 列:{err}"); } rowCount++; if (stop) { break; } } if (rowCount == 0) { errors.Add("沒有任何資料列"); } // 5. 回傳錯誤訊息清單 return errors; } public List ValidateColumns(IRow headerRow, out Dictionary columnMap) { List errors = new List(); columnMap = new Dictionary(); // 建立標題 → 欄位索引的對照表 if (headerRow != null) { for (int i = 0; i <= headerRow.LastCellNum; i++) { var cell = headerRow.GetCell(i); if (cell == null) continue; string title = cell.ToString().Trim(); if (!string.IsNullOrEmpty(title) && !columnMap.ContainsKey(title)) { columnMap[title] = i; } } } // 確認每個必要欄位都存在於標頭列 foreach (var column in columns) { if (!columnMap.ContainsKey(column.Title)) { errors.Add($"找不到欄位「{column.Title}」"); } } return errors; } public List ValidateRow(IRow dataRow, Type type, Dictionary columnMap, out bool stop) { stop = false; List errors = new List(); // 以欄位名稱取得每欄的值 var cellValues = new Dictionary(); foreach (var column in columns) { string cellValue = ""; if (columnMap.ContainsKey(column.Title)) { var cell = dataRow.GetCell(columnMap[column.Title]); cellValue = cell == null ? "" : excel.CellValueToString(cell); } cellValues[column.Title] = cellValue; } // 若所有欄位皆為空則回傳停止旗標 bool allEmpty = true; foreach (var v in cellValues.Values) { if (!string.IsNullOrEmpty(v)) { allEmpty = false; break; } } if (allEmpty) { stop = true; return errors; } foreach (var column in columns) { string cellValue = cellValues[column.Title]; if (column.Required && string.IsNullOrEmpty(cellValue)) { errors.Add($"欄位「{column.Title}」為必填欄位"); } // 應用 TransformFunction(如果設定) if (column.TransformFunction != null) { cellValue = column.TransformFunction(cellValue)?.ToString() ?? ""; } if (!string.IsNullOrEmpty(cellValue)) { var property = type.GetProperty(column.FieldName); if (property != null) { try { var targetType = property.PropertyType; if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>)) { targetType = Nullable.GetUnderlyingType(targetType); } if (targetType == typeof(DateTime)) { ParseExcelDate(cellValue); } else { Convert.ChangeType(cellValue, targetType); } } catch { var typeName = property.PropertyType.Name; if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { var underlying = Nullable.GetUnderlyingType(property.PropertyType); typeName = $"{underlying.Name}(可空)"; } errors.Add($"欄位「{column.Title}」的值「{cellValue}」應該是 {typeName} 類型".Replace("<", "<").Replace(">", ">")); } } } } return errors; } public List ReadRows(string filePath, int beginRow = 1) where T : new() { var workbook = LoadWorkbook(filePath); excel.Sheet = workbook.GetSheetAt(0); var result = new List(); // 從標頭列建立欄位名稱 → 欄位索引對照表 var columnMap = new Dictionary(); var headerRow = excel.Sheet.GetRow(0); if (headerRow != null) { for (int i = 0; i <= headerRow.LastCellNum; i++) { var cell = headerRow.GetCell(i); if (cell == null) continue; string title = cell.ToString().Trim(); if (!string.IsNullOrEmpty(title) && !columnMap.ContainsKey(title)) { columnMap[title] = i; } } } for (int r = beginRow; r <= excel.Sheet.LastRowNum; r++) { var row = excel.Sheet.GetRow(r); if (row == null) { break; } // 以欄位名稱取得每欄的值 var cellValues = new Dictionary(); foreach (var column in columns) { string cellValue = ""; if (columnMap.ContainsKey(column.Title)) { var cell = row.GetCell(columnMap[column.Title]); cellValue = cell == null ? "" : excel.CellValueToString(cell); } cellValues[column.Title] = cellValue; } // 若所有欄位皆為空則停止 bool allEmpty = true; foreach (var v in cellValues.Values) { if (!string.IsNullOrEmpty(v)) { allEmpty = false; break; } } if (allEmpty) { break; } var item = new T(); foreach (var column in columns) { string cellValue = cellValues[column.Title]; // 假設 T 有對應的屬性名稱與欄位標題相同 var property = typeof(T).GetProperty(column.FieldName); if (property != null) { object valueToSet = null; if (column.TransformFunction != null) { valueToSet = column.TransformFunction(cellValue); } else { if (string.IsNullOrEmpty(cellValue)) { if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { valueToSet = null; } else { // 對於非 Nullable 類型,如果為空則跳過設定 continue; } } else { var targetType = property.PropertyType; if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>)) { targetType = Nullable.GetUnderlyingType(targetType); } if (targetType == typeof(DateTime)) { valueToSet = ParseExcelDate(cellValue); } else { valueToSet = Convert.ChangeType(cellValue, targetType); } } } property.SetValue(item, valueToSet, null); } } result.Add(item); } return result; } private static DateTime ParseExcelDate(string value) { if (DateTime.TryParse(value, out DateTime dt)) { return dt; } if (double.TryParse(value, out double d) && d >= MinOADate && d <= MaxOADate) { return DateTime.FromOADate(d); } throw new FormatException($"無法將「{value}」轉換為日期"); } public virtual T SelectExisting(T item) where T : new() { throw new NotImplementedException("請在子類別中實作此方法以選取現有資料"); } public virtual void InsertOne(T item) where T : new() { throw new NotImplementedException("請在子類別中實作此方法以新增資料"); } public virtual void UpdateOne(T newItem, T existingItem) where T : new() { throw new NotImplementedException("請在子類別中實作此方法以更新資料"); } public virtual string ImportRowErrorMessage(T item, string errorMessage) where T : new() { return $"資料 {item.ToString()} 匯入失敗,錯誤原因:{errorMessage}"; } public bool Import(string filePath, out string message, int beginRow = 1) where T : new() { var importErrors = new List(); message = ""; var insertCount = 0; var updateCount = 0; var rows = ReadRows(filePath, beginRow); if (rows.Count == 0) { importErrors.Add("匯入檔案中沒有任何資料"); return false; } using (var ts = DAC.NewTransactionScope()) { foreach (var row in rows) { try { var existing = SelectExisting(row); if (existing == null) { InsertOne(row); insertCount++; } else { UpdateOne(row, existing); updateCount++; } } catch (Exception ex) { importErrors.Add(ImportRowErrorMessage(row, ex.Message)); } } } if (importErrors.Count == 0) { message = $"資料匯入成功,共新增 {insertCount} 筆資料,修改 {updateCount} 筆資料。"; return true; } else { message = $"資料匯入完成,共新增 {insertCount} 筆資料,修改 {updateCount} 筆資料,但有以下錯誤:
" + string.Join("
", importErrors.ToArray()); return false; } } /// /// 真假值轉換函數,把代表真值的字串轉換成 1,否則轉換成 0 /// protected TransformFunction BoolTransform = (input) => { if (string.IsNullOrEmpty(input)) return 0; var inputStr = input.ToUpper(); return trueValues.Contains(inputStr) ? 1 : 0; }; } // 新增委派定義 public delegate object TransformFunction(string input); public class ImportColumn { public string Title { get; set; } public int Width { get; set; } public string FieldName { get; set; } public bool Required { get; set; } public TransformFunction TransformFunction { get; set; } public ImportColumn(string title, int width, bool required = false, string fieldName = null, TransformFunction transformFunction = null) { Title = title; Width = width; Required = required; FieldName = fieldName ?? title; TransformFunction = transformFunction; } } }