反射的应用,使用TypeDescriptor.GetProperties方法,实现把List<T>转换成DataTable

本文介绍了如何使用C#的TypeDescriptor.GetProperties方法,配合DisplayNameAttribute,将ExportDocumentModel对象列表转换为DataTable,以便于数据展示和处理。

 使用TypeDescriptor.GetProperties方法,实现把List<T>对象转换成DataTable

using System;
using System.ComponentModel;
namespace Document.Data {
 public class ExportDocumentModel {
        [DisplayName("Id")]
        public int Id { get; set; }

        [DisplayName("Title")]
        public string Title { get; set; }
   
        [DisplayName("CreateDate")]
        public DateTime CreateDate { get; set; }
  

  }
}
        public DataTable ConvertModelToDataTable(List<ExportDocumentModel> exportDocumentModels) {
            DataTable dataTable = new DataTable();
            var propertyDescriptorCollection = TypeDescriptor.GetProperties(new ExportDocumentModel());//通过反射获取该类的属性集合
            foreach (PropertyDescriptor propertyDescriptor in propertyDescriptorCollection) {
                DisplayNameAttribute displayNameAttribute = propertyDescriptor.Attributes[typeof(DisplayNameAttribute)] as DisplayNameAttribute;//获取其中的DisplayNameAttribute属性
                if (displayNameAttribute != null) {
                    dataTable.Columns.Add(displayNameAttribute.DisplayName);//获取属性名称,赋值为DataTable列头
                }
            }
            foreach (ExportDocumentModel exportDocumentModel in exportDocumentModels) {
                DataRow dataRow = dataTable.NewRow();
                foreach (PropertyDescriptor propertyDescriptor in propertyDescriptorCollection) {
                    DisplayNameAttribute displayNameAttribute = propertyDescriptor.Attributes[typeof(DisplayNameAttribute)] as DisplayNameAttribute;
                    if (displayNameAttribute != null) {
                        var val = exportDocumentModel.GetPropertyValue(propertyDescriptor.Name);//根据属性名称获取属性值
                        dataRow[displayNameAttribute.DisplayName] = val;//根据属性名称为DataTable中对应的列赋值
                    }
                }
                dataTable.Rows.Add(dataRow);
            }
            return dataTable;
        }

 

<think> 我们正在分析 `CreateUserCol` 方法。根据代码,该方法的作用是创建数据列并配置网格的列结构。下面我们将逐步分析其功能、作用以及可能存在的问题。 ### 1. **方法功能解析** ```csharp private void CreateUserCol() { try { List<DataColumn> columnList = new List<DataColumn>(); DataColumn column; // 遍历指定类型(this.m_Type)的所有属性 foreach (PropertyDescriptor desc in TypeDescriptor.GetProperties(this.m_Type)) { // 只处理可浏览的属性(即Browsable为true的属性) if (desc.IsBrowsable == true) { // 创建DataColumn,列名为属性名,类型为字符串 column = new DataColumn(desc.Name, typeof(string)); columnList.Add(column); } } // 将列集合赋值给网格的GridColumnList属性 this.grid.GridColumnList = columnList.ToArray(); // 创建根表、数据表,并设置列标题 this.grid.CreateRootTable(); this.grid.CreateDataTable(); this.grid.SetGridColumnCaption(); // 设置列标题 } catch (Exception ex) { CommonUtility.WriteErrorLog(m_Name, System.Reflection.MethodBase.GetCurrentMethod().Name + "()", ex); } } ``` #### 关键步骤: - **遍历类型的属性**:使用 `TypeDescriptor.GetProperties(this.m_Type)` 获取 `m_Type` 的所有属性描述符。 - **过滤可浏览属性**:通过 `desc.IsBrowsable` 筛选出需要显示的属性(通常对应数据模型的字段)。 - **创建数据列**:为每个属性创建一个 `DataColumn`,列名与属性名相同,列数据类型统一为 `string`。 - **配置网格列**:将生成的列集合赋给网格控件的 `GridColumnList` 属性。 - **初始化网格**: - `CreateRootTable()`:创建根表(可能用于分层结构)。 - `CreateDataTable()`:创建数据表(绑定到网格的数据源)。 - `SetGridColumnCaption()`:设置列标题(可能根据本地化资源或属性描述调整显示文本)。 ### 2. **方法的核心作用** - **动态构建网格列**:根据传入的类型 `m_Type`(如 `PROCESSDATASPEC`)动态生成网格的列,实现类型与UI的自动映射。 - **数据展示准备**:为后续数据绑定(如从Excel导入的数据)提供列结构支持。 - **灵活性**:通过反射类型属性,避免硬编码列名,提升代码可维护性。 ### 3. **潜在问题分析** - **数据类型强制换问题**:所有列都被创建为 `string` 类型: ```csharp column = new DataColumn(desc.Name, typeof(string)); // 始终为字符串类型 ``` - 如果原始属性是数值、日期等类型,网格显示时需额外换(易出错)。 - 数据绑定时可能出现类型不匹配异常(参考引用[3]中关于 `DBNull` 的约束)[^3]。 - **忽略属性类型**:未根据属性实际类型(`desc.PropertyType`)创建列,导致: - 网格无法自动应用格式(如日期格式)。 - 排序、筛选等功能可能失效(字符串与数字排序规则不同)。 - **性能问题**:频繁反射 `TypeDescriptor.GetProperties` 可能影响效率(尤其在频繁调用时)。 - **异常处理不足**:仅捕获所有异常并写日志,未区分处理特定异常(如属性访问失败)。 ### 4. **改进建议** - **按实际类型创建列**: ```csharp column = new DataColumn(desc.Name, desc.PropertyType); // 使用属性原生类型 ``` - **处理可空类型**:若属性为可空类型(如 `int?`),需特殊处理: ```csharp Type colType = desc.PropertyType; if (colType.IsGenericType && colType.GetGenericTypeDefinition() == typeof(Nullable<>)) colType = Nullable.GetUnderlyingType(colType); column = new DataColumn(desc.Name, colType ?? typeof(string)); ``` - **优化异常处理**:增加具体异常类型(如 `InvalidOperationException`)的处理逻辑。 ### 5. **应用场景示例** 假设 `m_Type` 为 `PROCESSDATASPEC` 类: ```csharp public class PROCESSDATASPEC { [DisplayName("数据字段")] public string DataField { get; set; } [DisplayName("容差")] public int Tolerance { get; set; } // 原生int类型被为string会丢失语义 } ``` - 改进前:网格中 `Tolerance` 列按字符串排序(如 "1", "10", "2")而非数值顺序。 - 改进后:使用原生 `int` 类型列,网格可正确排序和筛选。 --- §§相关问题§§ 1. 如何使用 `PropertyDescriptor` 获取属性的 `DisplayName` 特性以设置列标题? 2. 如果某些属性需要隐藏(如密码字段),如何扩展 `CreateUserCol` 方法实现? 3. 在数据绑定场景中,如何处理 `DBNull.Value` 与可空类型的换以避免引用[3]中的错误[^3]? </think> ### `CreateUserCol` 方法功能与作用分析 #### 1. **核心功能:动态创建网格列** ```csharp List<DataColumn> columnList = new List<DataColumn>(); foreach (PropertyDescriptor desc in TypeDescriptor.GetProperties(this.m_Type)) { if (desc.IsBrowsable == true) { column = new DataColumn(desc.Name, typeof(string)); // 创建字符串类型列 columnList.Add(column); } } ``` - **反射类型属性**:通过 `TypeDescriptor.GetProperties(this.m_Type)` 获取绑定类型(如 `PROCESSDATASPEC`)的所有可浏览属性[^1] - **动态生成列**:为每个属性创建对应的 `DataColumn`,列名与属性名保持一致 - **数据类型处理**:所有列强制设为 `string` 类型(`typeof(string)`),简化数据导入但丢失原始类型信息 #### 2. **UI 网格初始化流程** ```csharp this.grid.GridColumnList = columnList.ToArray(); // 设置列集合 this.grid.CreateRootTable(); // 创建基础表结构 this.grid.CreateDataTable(); // 初始化数据表 this.grid.SetGridColumnCaption();// 设置列显示标题 ``` - **四步初始化**: 1. 注入动态生成的列集合 2. 创建根表结构(可能包含父子表关系) 3. 生成实际数据表(用于存储导入数据) 4. 本地化列标题(如将属性名为多语言显示文本) #### 3. **关键设计特点** - **类型驱动UI**:基于 `m_Type`(如 `PROCESSDATASPEC`)动态构建网格列,实现类型与UI的自动映射 - **可扩展性**:只需更换 `m_Type` 即可适配不同数据结构,无需修改网格逻辑 - **属性过滤**:通过 `IsBrowsable` 过滤敏感或不需显示的属性(如密码字段) #### 4. **潜在问题与风险** - **类型丢失问题**(最严重): ```csharp column = new DataColumn(desc.Name, typeof(string)); // 强制为字符串 ``` - 数值/日期等类型被为字符串,导致: - 排序错误(如 "10" < "2") - 计算功能失效(无法求和/平均值) - 格式混乱(日期显示为 "20240101") - **对比引用[3]**:若导入数据包含 `DBNull`,会触发类似 `Column 'X' does not allow DBNull.Value` 的异常[^3] - **性能隐患**: - 反射操作 `TypeDescriptor.GetProperties` 在频繁调用时影响性能 - 每次初始化都重建整个网格结构,大数据量时效率低 - **数据校验缺失**: - 未继承数据注解(如 `[Required]`) - 未设置约束(如 `AllowDBNull=false`) #### 5. **优化建议** ```csharp // 改进方案:保留原始数据类型 column = new DataColumn(desc.Name, desc.PropertyType); // 添加数据注解支持 if (desc.Attributes.Contains(typeof(RequiredAttribute))) column.AllowDBNull = false; ``` - 保留原生类型:使用 `desc.PropertyType` 而非 `typeof(string)` - 增加校验逻辑:解析 `RequiredAttribute` 等数据注解 - 缓存列结构:静态缓存常见类型的列定义 --- ### 方法执行流程图示 ```mermaid graph TD A[开始] --> B[遍历m_Type属性] B --> C{IsBrowsable?} C -->|是| D[创建DataColumn<br/>列名=属性名<br/>类型=string] C -->|否| B D --> E[加入列集合] E --> B B -->|遍历完成| F[网格绑定列集合] F --> G[创建根表] G --> H[创建数据表] H --> I[设置列标题] I --> J[结束] ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值