翼度科技»论坛 编程开发 .net 查看内容

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

6

主题

6

帖子

18

积分

新手上路

Rank: 1

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

01、接口设计

上文中已经详细讲解了整体设计思路以及大致设计了需要哪些方法。下面我们先针对上文设计思想确定对外提供的接口。具体接口如下:
  1. //根据列名数组创建表格
  2. public static DataTable Create(string[] columnNames, string? tableName = null);
  3. //根据列名-类型键值对创建表格
  4. public static DataTable Create(Dictionary<string, Type> columns, string? tableName = null);
  5. //根据类创建表格
  6. //如果设置DescriptionAttribute,则将特性值作为表格的列名称
  7. //否则将属性名作为表格的列名称
  8. public static DataTable Create<T>(string? tableName = null);
  9. //把表格转换为实体对象集合
  10. //如果设置DescriptionAttribute,则将特性值作为表格的列名称
  11. //否则将属性名作为表格的列名称
  12. public static IEnumerable<T> ToModels<T>(DataTable dataTable);
  13. //把实体对象集合转为表格
  14. //如果设置DescriptionAttribute,则将特性值作为表格的列名称
  15. //否则将属性名作为表格的列名称
  16. public static DataTable ToDataTable<T>(IEnumerable<T> models, string? tableName = null);
  17. //把一维数组作为一列转换为表格
  18. public static DataTable ToDataTableWithColumnArray<TColumn>(TColumn[] array, string? tableName = null, string? columnName = null);
  19. //把一维数组作为一行转换为表格
  20. public static DataTable ToDataTableWithRowArray<TRow>(TRow[] array, string? tableName = null);
  21. //行列转置
  22. public static DataTable Transpose(DataTable dataTable, bool isColumnNameAsData = true);
复制代码
02、根据列名数组创建表格

该方法实现比较简单,我们直接看代码:
  1. //根据列名数组创建表格
  2. public static DataTable Create(string[] columnNames, string? tableName = null)
  3. {
  4.     var table = new DataTable(tableName);
  5.     foreach (var columnName in columnNames)
  6.     {
  7.         table.Columns.Add(columnName);
  8.     }
  9.     return table;
  10. }
复制代码
我们进行一个简单的单元测试:
  1. [Fact]
  2. public void Create()
  3. {
  4.     //正常创建成功
  5.     var columnNames = new string[] { "A", "B" };
  6.     var table = TableHelper.Create(columnNames);
  7.     Assert.Equal("", table.TableName);
  8.     Assert.Equal(2, table.Columns.Count);
  9.     Assert.Equal(columnNames[0], table.Columns[0].ColumnName);
  10.     Assert.Equal(columnNames[1], table.Columns[1].ColumnName);
  11.     Assert.Equal(typeof(string), table.Columns[0].DataType);
  12.     Assert.Equal(typeof(string), table.Columns[1].DataType);
  13.     //验证表名
  14.     table = TableHelper.Create(columnNames, "test");
  15.     Assert.Equal("test", table.TableName);
  16.     //验证列名不能重复
  17.     columnNames = new string[] { "A", "A" };
  18.     Assert.Throws<DuplicateNameException>(() => TableHelper.Create(columnNames));
  19. }
复制代码
03、根据列名-类型键值对创建表格

此方法是上一个方法的补充,默认直接根据列名创建表格,则所有列的数据类型都是string类型,而此方法可以指定每列的数据类型,实现也很简单,代码如下:
  1. //根据列名-类型键值对创建表格
  2. public static DataTable Create(Dictionary<string, Type> columns, string? tableName = null)
  3. {
  4.     var table = new DataTable(tableName);
  5.     foreach (var column in columns)
  6.     {
  7.         table.Columns.Add(column.Key, column.Value);
  8.     }
  9.     return table;
  10. }
复制代码
04、根据类创建表格

该方法是将类的属性名作为表格的列名称,属性对应的类型作为表格列的数据类型,把类转为表格。
同时我们需要约束类只能为结构体或类,而不能是枚举、基础类型、以及集合类型、委托、接口等。显然我们很难通过泛型约束达到我们的需求,因此我们首先需要对该方法的泛型进行类型校验。
校验成功后,我们只需要通过反射即可拿到类的所有属性信息,即可创建表格。同时我们约定如果属性设置了DescriptionAttribute特性,则特性值作为列名,如果没有设置特性则取属性名称作为列名。
  1. //根据类创建表格
  2. //如果设置DescriptionAttribute,则将特性值作为表格的列名称
  3. //否则将属性名作为表格的列名称
  4. public static DataTable Create<T>(string? tableName = null)
  5. {
  6.     //T必须是结构体或类,并且不能是集合类型
  7.     AssertTypeValid<T>();
  8.     //获取类的所有公共属性
  9.     var properties = typeof(T).GetProperties();
  10.     var columns = new Dictionary<string, Type>();
  11.     foreach (var property in properties)
  12.     {
  13.         //根据属性获取列名
  14.         var columnName = GetColumnName(property);
  15.         //组织列名-类型键值对
  16.         columns.Add(columnName, property.PropertyType);
  17.     }
  18.     return Create(columns, tableName);
  19. }
  20. //断言类型有效性
  21. private static void AssertTypeValid<T>()
  22. {
  23.     var type = typeof(T);
  24.     if (type.IsValueType && !type.IsEnum && !type.IsPrimitive)
  25.     {
  26.         //是值类型,但是不是枚举或基础类型
  27.         return;
  28.     }
  29.     else if (typeof(T).IsClass && !typeof(IEnumerable).IsAssignableFrom(typeof(T)))
  30.     {
  31.         //是类类型,但是不是集合类型,同时也不是委托、接口类型
  32.         return;
  33.     }
  34.     throw new InvalidOperationException("T must be a struct or class and cannot be a collection type.");
  35. }
  36. //根据属性获取列名称
  37. private static string GetColumnName(PropertyInfo property)
  38. {
  39.     //获取描述特性
  40.     var attribute = property.GetCustomAttribute<DescriptionAttribute>();
  41.     //如果存在描述特性则返回描述,否则返回属性名称
  42.     return attribute?.Description ?? property.Name;
  43. }
复制代码
下面我们针对枚举、字符串,基础类型、集合等情况进行详细的单元测试,代码如下:
  1. [Fact]
  2. public void Create_T()
  3. {
  4.     //验证枚举
  5.     var expectedMessage = "T must be a struct or class and cannot be a collection type.";
  6.     var exception1 = Assert.Throws<InvalidOperationException>(() => TableHelper.Create<StatusEnum>());
  7.     Assert.Equal(expectedMessage, exception1.Message);
  8.     //验证基础类型
  9.     var exception2 = Assert.Throws<InvalidOperationException>(() => TableHelper.Create<int>());
  10.     Assert.Equal(expectedMessage, exception2.Message);
  11.     //验证字符串类型
  12.     var exception3 = Assert.Throws<InvalidOperationException>(() => TableHelper.Create<string>());
  13.     Assert.Equal(expectedMessage, exception3.Message);
  14.     //验证集合
  15.     var exception4 = Assert.Throws<InvalidOperationException>(() => TableHelper.Create<Dictionary<string, Type>>());
  16.     Assert.Equal(expectedMessage, exception4.Message);
  17.     //验证正常情况
  18.     var table = TableHelper.Create<Student<double>>();
  19.     Assert.Equal("", table.TableName);
  20.     Assert.Equal(3, table.Columns.Count);
  21.     Assert.Equal("标识", table.Columns[0].ColumnName);
  22.     Assert.Equal("姓名", table.Columns[1].ColumnName);
  23.     Assert.Equal("Age", table.Columns[2].ColumnName);
  24.     Assert.Equal(typeof(string), table.Columns[0].DataType);
  25.     Assert.Equal(typeof(string), table.Columns[1].DataType);
  26.     Assert.Equal(typeof(double), table.Columns[2].DataType);
  27. }
复制代码
:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Ideal

来源:https://www.cnblogs.com/hugogoos/p/18577563
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具