C# 使用 Aspose.Cells 库读写 Excel 与 CSV 文件的通用实现
在使用 C# 进行数据处理时,经常需要将结构化数据在内存对象与 Excel/CSV 文件之间转换。借助第三方组件 Aspose.Cells,可以通过泛型与反射技术,实现不依赖具体业务类的通用读写方法。本文将展示如何定义通用的读取接口(支持 CSV 和 xlsx)以及批量写入逻辑,涵盖文件校验、动态属性映射、数据类型转换及结果保存等核心步骤。
实现目标与准备
该方案主要解决两个问题:一是从文件或流中读取表格数据并将其转换为强类型对象列表;二是将对象列表直接导出为标准电子表格格式。为了确保代码的通用性,输入的文件路径需包含完整的扩展名(.csv 或 .xlsx)。在调用任何方法之前,请确保项目中已引用 Aspose.Cells 命名空间。
读取 Excel/CSV 数据至对象列表
第1步:构建泛型读取方法
此方法的目的是根据传入的 T 类型,自动匹配 Excel 第一行的列名(即表头),将每一行数据实例化并为对应属性赋值。若表头名称与类属性名一致(不区分大小写),即可自动填充。
- 定义一个静态或实例方法
GetObjectList<T>,其中泛型约束为where T : new(),以保证能够创建无参实例。 - 对传入的
filePath进行合法性检查。仅允许以.csv或.xlsx结尾的路径,否则直接返回空集合。 - 初始化
Workbook实例加载源文件,并获取第一个工作表Worksheet。 - 调用辅助方法
GetTitleDic(sheet),遍历表头行(第0行),建立Dictionary<string, int>,键为列名,值为对应的列索引。
第2步:逐行解析与数据转换
从第1行开始循环遍历工作表中的行:
- 终止条件:如果当前行首列(
[i, 0])内容为空,则说明数据结束,跳出循环。 - 获取当前对象的类型信息
Type type = typeof(T),并通过type.GetProperties()获取所有公开属性。 - 遍历每个属性,若在标题字典中存在对应的列名,则获取该单元格文本值。
- 类型转换逻辑:
- 若属性类型为
string,直接使用单元格文本值。 - 若属性类型为
Enum,尝试将文本值转换为整型后赋值给枚举字段。 - 对于其他类型,优先查找该类型是否存在名为
Parse且参数为string的方法,通过反射调用该方法进行解析(例如int,DateTime等)。 - 如果在解析过程中抛出异常,则忽略该行数据在该属性的赋值(可根据需求调整错误处理策略,如记录日志)。
/// <summary>
/// 从Excel获取数据
/// </summary>
/// <typeparam name="T">对象</typeparam>
/// <param name="filePath">文件完整路径</param>
/// <returns>对象列表</returns>
public List<T> GetObjectList<T>(string filePath) where T : new()
{
List<T> list = new List<T>();
// 限制文件格式
if (!filePath.Trim().EndsWith("csv") && !filePath.Trim().EndsWith("xlsx"))
{
return list;
}
Type type = typeof(T);
Workbook workbook = new Workbook(filePath);
Worksheet sheet = workbook.Worksheets[0];
// 获取标题列名与索引的映射
var titleDic = GetTitleDic(sheet);
// 从第二行(索引1)开始逐行读取
for (int i = 1; i < int.MaxValue; i++)
{
// 遇到首列为空的行,判定数据结束
if (string.IsNullOrEmpty(sheet.Cells[i, 0].StringValue))
{
break;
}
T instance = new T();
// 遍历当前对象的所有属性进行赋值
foreach (var item in type.GetProperties())
{
if (titleDic.ContainsKey(item.Name))
{
string str = sheet.Cells[i, titleDic[item.Name]].StringValue;
if (!string.IsNullOrEmpty(str))
{
try
{
if (item.PropertyType == typeof(string))
{
item.SetValue(instance, str);
}
else if (item.PropertyType.IsEnum)
{
item.SetValue(instance, int.Parse(str));
}
else
{
MethodInfo method = item.PropertyType.GetMethod("Parse", new Type[] { typeof(string) });
if (method != null)
{
object obj = method.Invoke(null, new object[] { str });
item.SetValue(instance, obj);
}
}
}
catch (Exception)
{
// 此处可以捕获异常并根据需要记录日志,避免单行错误导致整体崩溃
}
}
}
}
list.Add(instance);
}
return list;
}
将对象列表导出为 Excel 文件
第3步:编写通用导出方法
此方法的目的是将任意对象列表序列化并输出到指定的文件中。首先检查文件后缀是否为 .csv 或 .xlsx,若不合法则直接返回。
接着创建新的 Workbook 实例,锁定默认的第一个工作表 Worksheet。
第4步:冻结首行与动态表头生成
- 设置冻结窗格:调用
sheet.FreezePanes(1, 1, 1, 0),这将使首行(表头)在向下滚动时保持固定。 - 当遍历到列表的第一个对象(
row == 0)时,执行表头写入:- 再次通过反射获取对象的属性集合。
- 注意此处使用了 BindingFlags 组合以精确控制获取范围,但在示例中保留了原有的基础写法:遍历每一个属性,将属性名写入当前行的每一列。
- 再次通过反射获取对象的属性集合。
第5步:逐行列入数据并保存
- 使用外层循环遍历对象列表,内层循环遍历当前对象的属性:
- 外层变量
row记录行号,内层变量column记录列号。 - 调用
property.GetValue(obj).ToString()获取属性值并将其转为字符串填入对应单元格。 - 每写完一行对象数据,
row++。 - 循环结束后,调用
workbook.Save(saveFilePath)将内存中的结构持久化为文件。
/// <summary>
/// 把对象List保存到Excel
/// </summary>
/// <typeparam name="T">对象</typeparam>
/// <param name="objList">对象列表</param>
/// <param name="saveFilePath">保存文件的完整路径,包括文件类型</param>
public void SetExcelList<T>(List<T> objList, string saveFilePath)
{
// 校验保存路径的格式要求
if (!saveFilePath.Trim().EndsWith("csv") && !saveFilePath.Trim().EndsWith("xlsx"))
{
return;
}
Workbook workbook = new Workbook();
Worksheet sheet = workbook.Worksheets[0];
// 冻结第一行以便阅读
sheet.FreezePanes(1, 1, 1, 0);
int row = 0;
foreach (var obj in objList)
{
int column = 0;
// 获取指定绑定标志的属性集合
var properties = obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly);
// 如果是第一行,负责写入列标题
if (row == 0)
{
foreach (var titName in properties)
{
sheet.Cells[0, column].PutValue(titName.Name);
column++;
}
row++;
}
// 写入当前行对象的属性值
column = 0;
foreach (var property in properties)
{
var itemValue = property.GetValue(obj);
sheet.Cells[row, column].PutValue(itemValue.ToString());
column++;
}
row++;
}
workbook.Save(saveFilePath);
}
/// <summary>
/// 辅助方法:获取标题行数据
/// </summary>
private Dictionary<string, int> GetTitleDic(Worksheet sheet)
{
Dictionary<string, int> titList = new Dictionary<string, int>();
// 遍历第一行找出非空的列作为键名
for (int i = 0; i < int.MaxValue; i++)
{
if (sheet.Cells[0, i].StringValue == string.Empty)
{
return titList;
}
titList.Add(sheet.Cells[0, i].StringValue, i);
}
return titList;
}
注意事项
- 路径校验:代码中强制要求路径必须以 .csv 或 .xlsx 结尾,这是为了避免在解析未知格式时发生不可预知的行为。
- 异常处理:在
GetObjectList内部使用了try-catch包裹单个属性的解析过程。这意味着如果某一行的某一列数据格式有误,该特定字段的赋值会失败,但不会中断整个数据的读取过程。 - 性能考量:由于大量使用了反射(Reflection),对于超大规模数据集(例如上万行以上的大表),读写速度可能不及特定的底层二进制读写器。但对于常规业务报表或小规模配置文件的处理,该方法提供了极佳的灵活性。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









