开源 - Ideal库 - Excel帮助类,TableHelper实现(二)

书接上回,我们今天开始实现对象集合与DataTable的相互转换。

01、接口设计

上文中已经详细讲解了整体设计思路以及大致设计了需要哪些方法。下面我们先针对上文设计思想确定对外提供的接口。具体接口如下:

csharp 复制代码
//根据列名数组创建表格
public static DataTable Create(string[] columnNames, string? tableName = null);

//根据列名-类型键值对创建表格
public static DataTable Create(Dictionary<string, Type> columns, string? tableName = null);

//根据类创建表格
//如果设置DescriptionAttribute,则将特性值作为表格的列名称
//否则将属性名作为表格的列名称
public static DataTable Create<T>(string? tableName = null);

//把表格转换为实体对象集合
//如果设置DescriptionAttribute,则将特性值作为表格的列名称
//否则将属性名作为表格的列名称
public static IEnumerable<T> ToModels<T>(DataTable dataTable);

//把实体对象集合转为表格
//如果设置DescriptionAttribute,则将特性值作为表格的列名称
//否则将属性名作为表格的列名称
public static DataTable ToDataTable<T>(IEnumerable<T> models, string? tableName = null);

//把一维数组作为一列转换为表格
public static DataTable ToDataTableWithColumnArray<TColumn>(TColumn[] array, string? tableName = null, string? columnName = null);

//把一维数组作为一行转换为表格
public static DataTable ToDataTableWithRowArray<TRow>(TRow[] array, string? tableName = null);

//行列转置
public static DataTable Transpose(DataTable dataTable, bool isColumnNameAsData = true);

02、根据列名数组创建表格

该方法实现比较简单,我们直接看代码:

csharp 复制代码
//根据列名数组创建表格
public static DataTable Create(string[] columnNames, string? tableName = null)
{
    var table = new DataTable(tableName);
    foreach (var columnName in columnNames)
    {
        table.Columns.Add(columnName);
    }
    return table;
}

我们进行一个简单的单元测试:

csharp 复制代码
[Fact]
public void Create()
{
    //正常创建成功
    var columnNames = new string[] { "A", "B" };
    var table = TableHelper.Create(columnNames);
    Assert.Equal("", table.TableName);
    Assert.Equal(2, table.Columns.Count);
    Assert.Equal(columnNames[0], table.Columns[0].ColumnName);
    Assert.Equal(columnNames[1], table.Columns[1].ColumnName);
    Assert.Equal(typeof(string), table.Columns[0].DataType);
    Assert.Equal(typeof(string), table.Columns[1].DataType);

    //验证表名
    table = TableHelper.Create(columnNames, "test");
    Assert.Equal("test", table.TableName);

    //验证列名不能重复
    columnNames = new string[] { "A", "A" };
    Assert.Throws<DuplicateNameException>(() => TableHelper.Create(columnNames));
}

03、根据列名-类型键值对创建表格

此方法是上一个方法的补充,默认直接根据列名创建表格,则所有列的数据类型都是string类型,而此方法可以指定每列的数据类型,实现也很简单,代码如下:

csharp 复制代码
//根据列名-类型键值对创建表格
public static DataTable Create(Dictionary<string, Type> columns, string? tableName = null)
{
    var table = new DataTable(tableName);
    foreach (var column in columns)
    {
        table.Columns.Add(column.Key, column.Value);
    }
    return table;
}

04、根据类创建表格

该方法是将类的属性名作为表格的列名称,属性对应的类型作为表格列的数据类型,把类转为表格。

同时我们需要约束类只能为结构体或类,而不能是枚举、基础类型、以及集合类型、委托、接口等。显然我们很难通过泛型约束达到我们的需求,因此我们首先需要对该方法的泛型进行类型校验。

校验成功后,我们只需要通过反射即可拿到类的所有属性信息,即可创建表格。同时我们约定如果属性设置了DescriptionAttribute特性,则特性值作为列名,如果没有设置特性则取属性名称作为列名。

csharp 复制代码
//根据类创建表格
//如果设置DescriptionAttribute,则将特性值作为表格的列名称
//否则将属性名作为表格的列名称
public static DataTable Create<T>(string? tableName = null)
{
    //T必须是结构体或类,并且不能是集合类型
    AssertTypeValid<T>();
    //获取类的所有公共属性
    var properties = typeof(T).GetProperties();
    var columns = new Dictionary<string, Type>();
    foreach (var property in properties)
    {
        //根据属性获取列名
        var columnName = GetColumnName(property);
        //组织列名-类型键值对
        columns.Add(columnName, property.PropertyType);
    }
    return Create(columns, tableName);
}

//断言类型有效性
private static void AssertTypeValid<T>()
{
    var type = typeof(T);
    if (type.IsValueType && !type.IsEnum && !type.IsPrimitive)
    {
        //是值类型,但是不是枚举或基础类型
        return;
    }
    else if (typeof(T).IsClass && !typeof(IEnumerable).IsAssignableFrom(typeof(T)))
    {
        //是类类型,但是不是集合类型,同时也不是委托、接口类型
        return;
    }
    throw new InvalidOperationException("T must be a struct or class and cannot be a collection type.");
}

//根据属性获取列名称
private static string GetColumnName(PropertyInfo property)
{
    //获取描述特性
    var attribute = property.GetCustomAttribute<DescriptionAttribute>();
    //如果存在描述特性则返回描述,否则返回属性名称
    return attribute?.Description ?? property.Name;
}

下面我们针对枚举、字符串,基础类型、集合等情况进行详细的单元测试,代码如下:

csharp 复制代码
[Fact]
public void Create_T()
{
    //验证枚举
    var expectedMessage = "T must be a struct or class and cannot be a collection type.";
    var exception1 = Assert.Throws<InvalidOperationException>(() => TableHelper.Create<StatusEnum>());
    Assert.Equal(expectedMessage, exception1.Message);

    //验证基础类型
    var exception2 = Assert.Throws<InvalidOperationException>(() => TableHelper.Create<int>());
    Assert.Equal(expectedMessage, exception2.Message);

    //验证字符串类型
    var exception3 = Assert.Throws<InvalidOperationException>(() => TableHelper.Create<string>());
    Assert.Equal(expectedMessage, exception3.Message);

    //验证集合
    var exception4 = Assert.Throws<InvalidOperationException>(() => TableHelper.Create<Dictionary<string, Type>>());
    Assert.Equal(expectedMessage, exception4.Message);

    //验证正常情况
    var table = TableHelper.Create<Student<double>>();
    Assert.Equal("", table.TableName);
    Assert.Equal(3, table.Columns.Count);
    Assert.Equal("标识", table.Columns[0].ColumnName);
    Assert.Equal("姓名", table.Columns[1].ColumnName);
    Assert.Equal("Age", table.Columns[2].ColumnName);
    Assert.Equal(typeof(string), table.Columns[0].DataType);
    Assert.Equal(typeof(string), table.Columns[1].DataType);
    Assert.Equal(typeof(double), table.Columns[2].DataType);
}

:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Ideal

相关推荐
fthux1 小时前
装闭 RenoPit 源码解析(13):生成AI装修闭坑PDF报告
人工智能·ai·pdf·开源·github
TunerT_TQ2 小时前
Valhalla 静态工程审阅 #007|AgentENV 源码证据驱动评测【大厂开源基础设施特辑】
rust·开源·go·github·sandbox·分布式系统·ai基础设施
NutShell Wang3 小时前
Vite 8.1 深度拆解:Rolldown 统一打包器如何终结前端构建的「双引擎时代」
前端·rust·开源·vite·开发者工具·vibe coding
tokenKe4 小时前
OpenWork:把 AI Agent 工作台 从订阅制变成开源 + 本地的革命 | SSP Github Daily
人工智能·开源·github
小马9264 小时前
把稀疏注意力做对:HiLS-Attention 如何在数学上同时打破效率与效果的天花板
开源·aigc
TunerT_TQ4 小时前
GitHub深度工程评测:Cline 源码级工程评测|65k Star 开源AI编程助手的工程全景
typescript·开源·github·vs code·ai编程助手·开源供应链安全·静态源码审计
m4Rk_4 小时前
【论文阅读】Agent 记忆机制(26):Infini Memory——将长期记忆维护成可读写的主题文档
论文阅读·人工智能·学习·开源·github
hPw0eKIqD15 小时前
使用二次封装的Excel COM 组件操作Excel\WPS ET中的区域、行和列
excel·wps
冬奇Lab17 小时前
开源项目第172期:agentOS — 以库的形式给 AI Agent 一个操作系统,冷启动 92x 更快、内存 47x 更少
人工智能·开源·agent
冰心孤城1 天前
Excel: xls与xlsx格式转换排坑指南
java·前端·excel