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

01、接口设计

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

//根据列名数组创建表格
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、根据列名数组创建表格

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

//根据列名数组创建表格
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;
}

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

[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类型,而此方法可以指定每列的数据类型,实现也很简单,代码如下:

//根据列名-类型键值对创建表格
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特性,则特性值作为列名,如果没有设置特性则取属性名称作为列名。

//根据类创建表格
//如果设置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;
}

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

[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

开源 - Ideal库 - Excel帮助类,TableHelper实现(二)的更多相关文章

  1. Pugixml一种快速解析XML文件的开源解析库

    Pugixml是一个轻量级的C++ XML开源解析库,DOM形式的解析器.接口和丰富的遍历和修改操作,快速的解析,此外支持XPath1.0实现数据查询,支持unicode编码: 使用Pugixml可通 ...

  2. 导入导出Excel工具类ExcelUtil

    前言 前段时间做的分布式集成平台项目中,许多模块都用到了导入导出Excel的功能,于是决定封装一个ExcelUtil类,专门用来处理Excel的导入和导出 本项目的持久化层用的是JPA(底层用hibe ...

  3. asp.net(C#) Excel导出类 导出.xls文件

    ---恢复内容开始--- using Microsoft.Office.Interop.Excel; 针对office 2003需添加引用Microsoft   Excel   11.0   Obje ...

  4. 从Google开源RE2库学习到的C++测试方案

    最近因为科研需求,一直在研究Google的开源RE2库(正则表达式识别库),库源码体积庞大,用C++写的,对于我这个以前专供Java的人来说真的是一件很痛苦的事,每天只能啃一点点.今天研究了下里面用到 ...

  5. 爆料喽!!!开源日志库Logger的剖析分析

    导读 Logger类提供了多种方法来处理日志活动.上一篇介绍了开源日志库Logger的使用,今天我主要来分析Logger实现的原理. 库的整体架构图 详细剖析 我们从使用的角度来对Logger库抽茧剥 ...

  6. 爆料喽!!!开源日志库Logger的使用秘籍

    日志对于开发来说是非常重要的,不管是调试数据查看.bug问题追踪定位.数据信息收集统计,日常工作运行维护等等,都大量的使用到.今天介绍著名开源日志库Logger的使用,库的地址:https://git ...

  7. [C#技术] .NET平台开源JSON库LitJSON的使用方法

    一个简单示例: String str = "{’name’:’cyf’,’id’:10,’items’:[{’itemid’:1001,’itemname’:’hello’},{’itemi ...

  8. arcgis api for js之echarts开源js库实现地图统计图分析

    前面写过一篇关于arcgis api for js实现地图统计图的,具体见:http://www.cnblogs.com/giserhome/p/6727593.html 那是基于dojo组件来实现图 ...

  9. arcgis api 3.x for js 之 echarts 开源 js 库实现地图统计图分析(附源码下载)

    前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...

  10. 开源 JSON 库解析性能对比( Jackson / Json.simple / Gson )

    Json 已成为当前服务器与 web 应用之间数据传输的公认标准. 微服务及分布式架构经常会使用 Json 来传输此类文件,因为这已经是 webAPI 的事实标准. 不过正如许多我们习以为常的事情一样 ...

随机推荐

  1. Storybook version8 智能化构建组件文档与单元测试

    根据官方文档说法,storybook 是一个独立构建前端UI组件与页面的车间. Storybook is a frontend workshop for building UI components ...

  2. webpack笔记-loader的详细使用介绍(四)

    loader 基本上都是第三方类库,使用时需要安装,有一些 loader 还需要安装额外的类库,例如 less-loader 需要 less,babel-loader 需要 babel 等. load ...

  3. 算法学习-CDQ分治

    对于二维偏序,为普通的求逆序对,只需要先排序一遍,然后树状数组或双指针即可 而三位偏序甚至更高,则需要用 CDQ 分治,简单来说,就是将树状数组和双指针结合 操作步骤如下: 1.开始将数组按第一维排序 ...

  4. 【QT性能优化】QT性能优化之QT6框架高性能图形视图框架快速展示百万图元大规模场景

    QT性能优化之QT6框架高性能图形视图框架快速展示百万图元大规模场景 简介: 本文展示了使用QT图形视图框架在一个场景中绘制出百万个图元的程序的效果以及源代码:本文还介绍了QT图形视图框架的一些实用功 ...

  5. 《Vue.js 设计与实现》读书笔记 - 第12章、组件的实现原理

    第12章.组件的实现原理 12.1 渲染组件 在渲染器内部的实现看,一个组件是一个特殊类型的虚拟 DOM 节点.之前在 patch 我们判断了 VNode 的 type 值来处理,现在来处理类型为对象 ...

  6. springmvc参数传递不给参数值默认值设置方法

    @RequestMapping("hello") public voiid test001(@RequestParam(defaultValue = "11") ...

  7. These dependencies were not found: * core-js/modules/es.array.push.js in ./node_modules/@babel/runtime/helpers/esm/regeneratorRuntime.js

    yarn add core-js  安装core-js包 : 出现这个问题的原因:因为vue-admin-template的package.json里没写这个包core-js,然后再咱们yarn装包的 ...

  8. .NET程序获取当前IP经纬度,并通过经纬度实现天气查询功能

      创建一个.net 8的webapi项目备用   编辑一个实体类,该实体类用于存储获取ip地址的经纬度数据使用   继续编辑三个类,用来存储对应经纬度的具体天气数据包:   改造默认的天气控制器,里 ...

  9. HEU KMS:一款超稳的MS Windows激活工具

    HEU KMS ACTIVATOR是一款功能齐全的免费Windows和Office系列激活工具. 打开程序,一步到位.直接点击开始就可以准备激活,Windows11/10乃至更早的版本都可以通用,顺带 ...

  10. manim边学边做--极坐标平面

    PolarPlane,顾名思义,是用于创建极坐标平面的类. 与笛卡尔坐标系不同,极坐标系是基于角度和半径来定位点的,这里的每个点由一个角度和距离原点的距离表示. 在Manim中,PolarPlane通 ...