概述
NPOI 是 POI 项目的 .NET 版本。POI是一个开源的Java读写Excel、WORD等微软OLE2组件文档的项目。NPOI 可以在没有安装 Office 或者相应环境的机器上对 WORD/EXCEL 文档进行读写,包含了大部分EXCEL的特性(单元格样式、数据格式、公式等等)。(复制百度百科)
表格导入导出的操作经常会碰到,最近抽空专门封装了一下。
遵循的原则:
1. 一行数据对应一个数据实体,需提供它们二者间的映射关系。
2. 支持合并单元格的导入导出(导出数据只支持竖向合并,导入数据遇到横向合并的单元格会抛出异常),图片未支持
导出的复杂在于样式的控制,当解决这一问题后后续操作就很简单了,而样式解析相关的代码行数也真的是不少,因为对API的不熟悉,连写带测折腾了不少时间。
导入因为行为的多变性,要求程序员调用时将具体的读写逻辑以策略的形式传入。所以代码量相对来说要少得多。其实也是避开了一些难题,比如公式符号,数字的上标,下标的解析(尚不知道怎么解决)。
导出效果
表格导入
单元格数据的解析
1. 先依据单元格类的数据类型cell.CellType(枚举类型),读取其内部保存的数据,如果类型是公式的,则依据cell.CachedFormulaResultType再进行一次判别。
2. 从单元格读到数据后还再进一步完成一次类型的转换,将它转换为我们需要的类型。
///
/// 扩展
///
public static class IRowExtension
{
///
/// 读取表格中某单元格的值,如果单元格是合并单元格,则竖向合并的正常返回,横向合并的抛出异常
///
/// 要读取的数据类型
/// Excel中的某行数据
/// 行内的列下标
///
public static A GetValue(this IRow row, int index)
{
try
{
//获取单元格
ICell cell = row.GetCell(index);
//判别是否合并单元格
if (cell.IsMergedCell)
{
ISheet sheet = cell.Sheet;
for (int i = 0, length = sheet.NumMergedRegions; i < length; i++)
{
var cellRange = sheet.GetMergedRegion(i);
if (cell.ColumnIndex >= cellRange.FirstColumn
&& cell.ColumnIndex <= cellRange.LastColumn
&& cell.RowIndex >= cellRange.FirstRow
&& cell.RowIndex <= cellRange.LastRow)
{
if (cellRange.FirstColumn != cellRange.LastColumn)
{
throw new Exception("不能有横向合并的单元格");
}
cell = sheet.GetRow(cellRange.FirstRow).GetCell(cellRange.FirstColumn);
}
}
}
return GetValue(cell);
}
catch (Exception ex)
{
//读取单元格信息时出错
throw new Exception("出错位置的列下标:" + index + ",报错信息:" + ex.Message);
}
}
///
/// 读取表格中某单元格的值,如果单元格是合并单元格,则竖向合并的正常返回,横向合并的抛出异常
///
/// 要读取的数据类型
/// Excel中的某单元格
///
private static A GetValue(ICell cell)
{
//依据单元格数据类型读取数据,并强转为目标类型
switch (cell.CellType)
{
case CellType.Boolean:
return cell.BooleanCellValue.ChangeType();
case CellType.Error:
return ErrorEval.GetText(cell.ErrorCellValue).ChangeType();
case CellType.Numeric:
if (DateUtil.IsCellDateFormatted(cell))
{
return cell.DateCellValue.ChangeType();
}
else
{
return cell.NumericCellValue.ChangeType();
}
case CellType.String:
string strValue = cell.StringCellValue;
if (string.IsNullOrEmpty(strValue))
{
return default(A);
}
else
{
return strValue.ChangeType();
}
case CellType.Formula://公式
switch (cell.CachedFormulaResultType)
{
case CellType.Boolean:
return cell.BooleanCellValue.ChangeType();
case CellType.Error:
return ErrorEval.GetText(cell.ErrorCellValue).ChangeType();
case CellType.Numeric:
if (DateUtil.IsCellDateFormatted(cell))
{
return cell.DateCellValue.ChangeType();
}
else
{
return cell.NumericCellValue.ChangeType();
}
case CellType.String:
string str = cell.StringCellValue;
if (string.IsNullOrEmpty(str))
{
return default(A);
}
else
{
return str.ChangeType();
}
case CellType.Unknown:
case CellType.Blank:
default:
return default(A);
}
case CellType.Unknown:
case CellType.Blank:
default:
return default(A);
}
}
}
类型转换
///
/// 调用Convert.ChangeType
///
/// 要返回的类型
/// 待转换的值
/// 转换后的值
public static T ChangeType(this object token)
{
object result = ChangeType(token, typeof(T));
if (result == null)
{
return default(T);
}
return (T)result;
}
///
/// 把对象类型转换为指定类型
///
/// 要转换的值
/// 目标类型
/// 转化后的指定类型的对象
public static object ChangeType(this object value, Type targetType)
{
if (value == null)
{
return null;
}
if (targetType.IsNullableType())
{
targetType = targetType.GetUnderlyingType();
}
if (targetType.IsEnum)
{
return Enum.Parse(targetType, value.ToString());
}
if (targetType == typeof(Guid))
{
return Guid.Parse(value.ToString());
}
return Convert.ChangeType(value, targetType);
}
导出代码的主体部分
策略接口
///
/// 提供每行数据的操作策略(不同的实现方式,达成不同的导入目标,存储目标不必一定是数据库)
/// 通过GetValue读取某单元格数据
///
public interface ExcelImportHelper
{
///
/// 判断本行数据是否数据库中已存在(有则会进行修改,没有则会新建)
///
/// Excel中的某行数据
/// 不存在则返回null,存在则返回该项数据
T IsExist(IRow row);
///
/// 新数据添加到数据库
///
/// 已完成映射的实体类实例
void Add(T model);
///
/// 更新数据库中的已有数据
///
/// 已完成映射的实体类实例
void Update(T model);
///
/// 提供从表格行数据映射到实体类的方法(解析失败的时候直接要抛出异常)
///
/// Excel中的某行数据
/// 新建的或刚从数据库中读取到的实体类实例
/// 映射失败或数据为空时返回false,则不会录入数据库
bool Map(IRow row, T model);
}
其他部分代码
///
/// 表格导入数据库
/// 也可以是导入DataTable等形式,只要提供不同的策略
///
/// Model的类型
public class ExcelImport where T : new()
{
private ExcelImportHelper helper;
///
/// 构造方法
///
/// 操作策略
public ExcelImport(ExcelImportHelper helper)
{
this.helper = helper;
}
///
/// 处理结果
///
public struct Result
{
private int num; //导入行数
private int num_add; //新增行数
private int num_edit; //修改行数
///
/// 导入行数
///
public int Num
{
get
{
return num;
}
set
{
num = value;
}
}
///
/// 新增行数
///
public int Num_add
{
get
{
return num_add;
}
set
{
num_add = value;
}
}
///
/// 修改行数
///
public int Num_edit
{
get
{
return num_edit;
}
set
{
num_edit = value;
}
}
///
/// 构造函数
///
/// 导入行数
/// 新增行数
/// 修改行数
internal Result(int num, int num_add, int num_edit)
{
this.num = num;
this.num_add = num_add;
this.num_edit = num_edit;
}
}
#region 导入操作
///
/// 表格导入到数据库
///
/// 上传控件的name
/// 最大上传的文件大小(MB)
/// 表格头部所占的高度
/// 表中的数据(只读取表中第一个Sheet)
public Result Inport(string name, int maxLength, int headerRowsNum)
{
Stream stream = null;
try
{
HttpContext context = HttpContext.Current;
HttpRequest request = context.Request;
//验证文件格式大小,返回文件流
stream = GetInputStream(request, name, maxLength);
//逐行读取文件内容并执行导入
return Operation(stream, headerRowsNum);
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (stream != null)
{
stream.Close();
stream.Dispose();
}
}
}
private Result Operation(Stream stream,int headerRowsNum)
{
//操作结果
Result result = new Result(0, 0, 0);
//读取表格内容
try
{
using (stream)
{
//自动识别.xlsx(XSSFWorkbook),.xls(HSSFWorkbook)
IWorkbook workbook = WorkbookFactory.Create(stream);
if (workbook.NumberOfSheets == 0)
throw new Exception("报错信息:文件无数据");
//取第一张表
ISheet sheet = workbook.GetSheetAt(0);
if (sheet.PhysicalNumberOfRows == 0)
throw new Exception("报错信息:文件无数据");
//数据行数
int rowCount = sheet.LastRowNum;
//读取表中所有数据
for (int i = sheet.FirstRowNum + headerRowsNum; i <= rowCount; i++)
{
IRow row = sheet.GetRow(i);
if (row != null)
{
result.Num++;
T model = helper.IsExist(row);
if (model == null)
{
result.Num_add++;
//新建
model = new T();
bool ok = helper.Map(row, model);
if (ok)
{
helper.Add(model);
}
}
else
{
result.Num_edit++;
//修改
bool ok = helper.Map(row, model);
if (ok)
{
helper.Update(model);
}
}
}
}
}
return result;
}
catch (Exception ex)
{
//在异常信息中添加出错位置
throw new Exception("当前读取到第"+ result.Num + "行数据,已添加"+ result .Num_add+ "条,已更新"
+ result.Num_edit + "条," + ex.Message);
}
}
#endregion
}
调用
//策略
private class helper : ExcelImport.ExcelImportHelper
{
public void Add(B_NEWS model)
{
bll.Add(model);
}
public B_NEWS IsExist(IRow row)
{
return null;
}
public bool Map(IRow row, B_NEWS model)
{
model.id = row.GetValue(0);
model.c_info_title = row.GetValue(1);
model.c_info_content = row.GetValue(2);
model.d_list_date = row.GetValue(3);
model.d_add_date = row.GetValue(4);
model.n_is_active = (row.GetValue(5) ? 1 : 0);
model.int1 = row.GetValue(6);
return true;
}
public void Update(B_NEWS model)
{
bll.Update(model);
}
}
protected void daoru(object sender, EventArgs e)
{
new ExcelImport(new helper()).Inport("excel", 10, 2);
}
表格导出
调用者直接接触的操作类
public class ExcelExport
{
///
/// 每列数据的解析方法
///
private List> func = new List>();
private IExcelBuilder builder = new ExcelBuilder();
private string headJson;
///
/// 更换表格建造器
///
/// 自定义的建造器