DataTable转化为泛型集合

本文深入探讨了泛型集合的概念及其在实际项目中的运用,包括泛型集合如何提高代码的复用性和灵活性,以及如何将DataTable转换为泛型集合的具体实现。

        前一段时间,因为做机房重构基本思路都没有搞清楚,然后周围接触的概念太多了,就想一开始什么都加上,不堪重负,起步很慢。后来经亮哥提点,一点一点的加东西,就好很多了。


       对于泛型集合,前面有很多男神女神已经写的很详细了。但是知识自己总结的才能算是印脑子里了。我就写写自己的理解吧。

       看完关于泛型集合的相关资料(博客、设计模式、网上其他一些介绍。。。),理一理思路:

是什么?

    [泛型是具有占位符(类型参数)的类、结构、接口和方法。这些占位符是类、结构、接口和方法所存储或使用的一个或多个类型的占位符。

 

    泛型集合类可以将类型参数用作它所存储的对象的类型的占位符;类型参数作为其字段的类型和其方法的参数类型出现。]——MSDN






       泛型集合其实就是一个个的实体类集合,我们在没有接触这个概念前,数据库访问返回的类型有实体、DataTable、Boolean。下面是画了几张示意图:


<span style="font-size:18px;">D层</span>
<span style="font-size:18px;">Imports System.Data
Imports System.Data.SqlClient
Imports Entity

Public Class AddDeleteDAL

    Public Function SelectDAL(ByVal SUserInfo As Entity.Users) As  Entity.Users  '查询表
        Dim SU As New Entity.Users    '定义实体类

        '连接数据库读取数据,为DataGridView赋值
        Dim conn As New SqlConnection        '定义连接数据库
        Dim cmd As New SqlCommand            '定义数据库命令

        Dim dt As New DataTable
        conn = New SqlConnection(SQLUtil.connstring())       '实例化SQLUntil中返回的字符串,
        cmd.Connection = conn                 '为字符串赋值

        '查询字符串
        cmd.CommandText = "Select * From T_Users Where Level=@Level"

       
        cmd.Parameters.Add(New SqlParameter("@Level", SUserInfo.Level))
       
        cmd.CommandType = CommandType.Text

        conn.Open()            '打开数据库

        Dim ds As New DataSet    '定义一个dataset  
        Dim da As New SqlDataAdapter  '定义一个sqldataadapter  ,dataset和sql sever的桥梁
        da.SelectCommand = cmd
        '填充dataset  
        'da.Fill(ds)
        da.Fill(ds, "Userinfo")
        dt = ds.Tables("Userinfo")

        'Select Case
        conn.Close()
        Return dt              '返回值
    End Function</span>


有哪些好处?

        如果不用泛型集合,你要是查询有多少个用户的时候(添加删除用户),单个实体和Boolean肯定是不行的,DataTable倒是可以,可以把但是它因为高耦合(一个用户信息变化,就得修改DataTable),使系统运行的性能会不好.


怎么用?

        首先明白DataTable转化为泛型集合就是在转化类中写了一个转化方法,写好之后在D层实例化该类的返回类型。

Imports System.Collections.Generic  '增加泛型的命名空间
Imports System.Reflection '反射
Public Class GetGenerics
    '将DataTable转化为泛型集合会降低耦合度
    Public Shared Function ConvertToList(Of T As {New})(DataTable As DataTable) As IList(Of T) 'ConvertToList(Of T As {New}) 这里的New是用来约束T的,必须有,不然New T的时候会出现错误
        '实例化最终返回的实体集合
        Dim myList As New List(Of T)
        '得到实体类的类型名
        Dim myType As Type = GetType(T)
        'Dim myType As Type
        'myType = GetType(T)
        '定义行集
        Dim DataRow As DataRow
        '定义一个临时变量
        Dim TmpName As String = String.Empty

        '遍历DataTable的所有数据行
        For Each DataRow In DataTable.Rows

            '定义一个实体类的对象
            Dim myT As New T
            '定义属性集合
            Dim propertys() As PropertyInfo = myT.GetType().GetProperties()
            '定义一个对象pr
            Dim pr As PropertyInfo

            '遍历该对象的所有属性
            For Each pr In propertys
                TmpName = pr.Name       '将属性名称赋值给临时变量

                '检查DataTable是否包含此列(列名==对象的属性名)
                If (DataTable.Columns.Contains(TmpName)) Then  '将此属性与DataTable里的列名比较,查看DataTable是否包含此属性
                    '判断此属性是否有Setter(类)

                    If (pr.CanWrite = False) Then       '判断此属性是否可写,如果不可写,跳出本次循环,进入下一次循环;exit for 是退出整个循环
                        Continue For
                    End If

                    Dim value As Object = DataRow(TmpName)      '定义一个对象型的变量来保存列的值

                    If (value.ToString <> DBNull.Value.ToString()) Then     '如果非空,则赋给对象的属性
                        pr.SetValue(myT, value, Nothing)           '在运行期间,通过发射,动态地访问一个对象的属性
                    End If
                End If
            Next
            myList.Add(myT)
        Next
        Return myList       '返回实体集合
    End Function

End Class

D层代码:

<span style="font-size:18px;">Public Class AddDeleteDAL

    Public Function SelectDAL(ByVal SUserInfo As Entity.Users) As List(Of Entity.Users)   '查询表
        Dim SU As New Entity.Users    '定义实体类

        '连接数据库读取数据,为DataGridView赋值
        Dim conn As New SqlConnection        '定义连接数据库
        Dim cmd As New SqlCommand            '定义数据库命令

        Dim dt As New DataTable
        conn = New SqlConnection(SQLUtil.connstring())       '实例化SQLUntil中返回的字符串,
        cmd.Connection = conn                 '为字符串赋值

        '查询字符串
        cmd.CommandText = "Select * From T_Users Where Level=@Level
       
        cmd.Parameters.Add(New SqlParameter("@Level", SUserInfo.Level))
        cmd.CommandType = CommandType.Text

        conn.Open()            '打开数据库

        Dim ds As New DataSet    '定义一个dataset  
        Dim da As New SqlDataAdapter  '定义一个sqldataadapter  ,dataset和sql sever的桥梁
        da.SelectCommand = cmd
        '填充dataset  
        'da.Fill(ds)
        da.Fill(ds, "Userinfo")
        dt = ds.Tables("Userinfo")
        </span>
<span style="font-size:18px;"><span style="white-space:pre">	</span>Dim mylist As New List(Of Entity.Users)
        mylist = GetGenerics.ConvertToList(Of Entity.Users)(dt)</span>
<span style="font-size:18px;">
        conn.Close()
        Return mylist              '返回值
    End Function</span>


        最后返回的值就是泛型集合了。

        在这个学习过程中,我感受很多,由浅入深,一点一滴的积累,厚积薄发。

        

         有很多理解有误的地方,希望各位帅哥美女们给指点指点。


<think>根据用户需求,用户希望将BindingList<T>换为DataTable。我们已有将List<T>换为DataTable的方法(引用[2]),但用户需要的是BindingList<T>的换。由于BindingList<T>也实现了IEnumerable<T>接口,因此我们可以复用类似的方法。 注意:BindingList<T>是集合,所以我们可以将其视为IEnumerable<T>进行处理。因此,我们可以直接使用类似引用[2]的方法,但需要调整以处理BindingList<T>。 然而,引用[2]的方法中使用了`collection.ElementAt(i)`,这对于没有索引器的集合可能效率不高(例如,如果集合是链表,则ElementAt(i)会遍历到第i个元素)。BindingList<T>内部使用List<T>,所以支持索引,但为了通用性,我们可以使用foreach遍历。 改进方法:不使用ElementAt,而是直接遍历集合。 另外,引用[2]的方法中使用了`ArrayList`和`LoadDataRow`,我们可以优化为使用`DataRow`的`ItemArray`。 以下是换方法的步骤: 1. 获取类T的属性列表。 2. 创建DataTable,并根据属性添加列(列名和数据类)。 3. 遍历集合中的每个对象,对于每个对象,获取其每个属性的值,组成数组。 4. 将数组作为一行添加到DataTable中。 注意:如果集合为空,我们仍然需要创建包含列结构的空DataTable。 实现代码: ```csharp public static DataTable ToDataTable<T>(BindingList<T> bindingList) { DataTable dataTable = new DataTable(); PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T)); // 创建列 foreach (PropertyDescriptor prop in properties) { // 注意:如果属性是Nullable类,我们需要获取其基础类 Type columnType = prop.PropertyType; if (columnType.IsGenericType && columnType.GetGenericTypeDefinition() == typeof(Nullable<>)) { columnType = Nullable.GetUnderlyingType(columnType) ?? columnType; } dataTable.Columns.Add(prop.Name, columnType); } // 填充行 foreach (T item in bindingList) { DataRow row = dataTable.NewRow(); foreach (PropertyDescriptor prop in properties) { row[prop.Name] = prop.GetValue(item) ?? DBNull.Value; } dataTable.Rows.Add(row); } return dataTable; } ``` 但是,上面的方法使用了`PropertyDescriptor`,这是为了与BindingList的机制保持一致(因为BindingList通常用于数据绑定,使用PropertyDescriptor可以处理动态属性)。另外,我们处理了Nullable类,将列类设置为基础类,并允许DBNull。 另一种方法是使用反射(如引用[2]),但这里我们使用`PropertyDescriptorCollection`,因为它在数据绑定上下文中更常见。 然而,用户可能希望一个更通用的方法,不仅适用于BindingList,也适用于其他IEnumerable<T>。我们可以将参数类改为`IEnumerable<T>`,这样两者都可以使用。但用户明确要求BindingList<T>,我们可以提供。 但考虑到通用性,我们可以写一个适用于任何IEnumerable<T>的方法,包括BindingList<T>。 修改后的通用方法(同时支持List<T>和BindingList<T>等): ```csharp public static DataTable ToDataTable<T>(IEnumerable<T> collection) { DataTable dataTable = new DataTable(); PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T)); // 创建列 foreach (PropertyDescriptor prop in properties) { Type columnType = prop.PropertyType; // 处理Nullable类 if (columnType.IsGenericType && columnType.GetGenericTypeDefinition() == typeof(Nullable<>)) { columnType = Nullable.GetUnderlyingType(columnType) ?? columnType; } dataTable.Columns.Add(prop.Name, columnType); } foreach (T item in collection) { DataRow row = dataTable.NewRow(); foreach (PropertyDescriptor prop in properties) { object value = prop.GetValue(item); row[prop.Name] = value ?? DBNull.Value; } dataTable.Rows.Add(row); } return dataTable; } ``` 这样,无论是BindingList<T>还是List<T>,都可以使用这个方法。 注意:使用`TypeDescriptor.GetProperties`可以获取到类上带有`[Browsable(true)]`的属性(默认都是true),而使用`typeof(T).GetProperties()`则可能获取所有属性(包括不可浏览的)。在数据绑定中,通常使用`TypeDescriptor`,因为它考虑了组件模(如属性描述)。但如果你不需要考虑这些,也可以使用反射。 使用反射的版本(类似于引用[2]但优化了遍历): ```csharp public static DataTable ToDataTable<T>(IEnumerable<T> collection) { DataTable dataTable = new DataTable(); PropertyInfo[] properties = typeof(T).GetProperties(); // 创建列 foreach (PropertyInfo prop in properties) { Type columnType = prop.PropertyType; if (columnType.IsGenericType && columnType.GetGenericTypeDefinition() == typeof(Nullable<>)) { columnType = Nullable.GetUnderlyingType(columnType) ?? columnType; } dataTable.Columns.Add(prop.Name, columnType); } foreach (T item in collection) { DataRow row = dataTable.NewRow(); foreach (PropertyInfo prop in properties) { object value = prop.GetValue(item, null); row[prop.Name] = value ?? DBNull.Value; } dataTable.Rows.Add(row); } return dataTable; } ``` 以上两种方法都可以。使用`PropertyInfo`的反射版本可能更直接,且不依赖于`System.ComponentModel`(但`TypeDescriptor`在`System.ComponentModel`中,而WinForms项目通常会引用)。 用户可以根据自己的需求选择。由于用户之前提到BindingList(常用于WinForms数据绑定),所以使用`TypeDescriptor`可能更合适,但反射版本更通用。 考虑到用户的问题,我们提供一个适用于BindingList<T>的换方法,但实际我们更推荐使用通用版本(IEnumerable<T>),这样更灵活。 因此,我们可以这样回答用户: 您可以使用以下方法将BindingList<T>换为DataTable。该方法也适用于其他实现了IEnumerable<T>接口的集合。 代码示例(使用反射): ```csharp using System; using System.Collections.Generic; using System.Data; using System.Reflection; public static class DataHelper { public static DataTable ToDataTable<T>(IEnumerable<T> collection) { DataTable dataTable = new DataTable(); PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); // 创建列 foreach (PropertyInfo prop in properties) { // 跳过不能读的属性 if (!prop.CanRead) { continue; } Type columnType = prop.PropertyType; // 处理Nullable类 if (columnType.IsGenericType && columnType.GetGenericTypeDefinition() == typeof(Nullable<>)) { columnType = Nullable.GetUnderlyingType(columnType) ?? columnType; } dataTable.Columns.Add(prop.Name, columnType); } foreach (T item in collection) { DataRow row = dataTable.NewRow(); foreach (PropertyInfo prop in properties) { if (!prop.CanRead) { continue; } object value = prop.GetValue(item, null); row[prop.Name] = value == null ? DBNull.Value : value; } dataTable.Rows.Add(row); } return dataTable; } } ``` 使用示例: ```csharp BindingList<Person> bindingList = new BindingList<Person>(); // 添加一些元素... DataTable table = DataHelper.ToDataTable(bindingList); ``` 注意: 1. 该方法处理了可空类(Nullable<>),将列类设置为对应的基础类。 2. 对于值为null的属性,我们将其换为DBNull.Value,以避免DataRow中设置null时可能出现的异常。 3. 我们过滤了不可读的属性(没有get访问器的属性)。 如果属性是索引器(带参数的属性),则会被`GetProperties`获取到,但在调用`GetValue`时会出错。因此,我们应跳过索引器。我们可以通过检查`prop.GetIndexParameters().Length > 0`来跳过索引器。 修改代码,跳过索引器: ```csharp foreach (PropertyInfo prop in properties) { // 跳过索引器 if (prop.GetIndexParameters().Length > 0) continue; // 其他代码... } ``` 因此,完整的方法如下: ```csharp public static DataTable ToDataTable<T>(IEnumerable<T> collection) { DataTable dataTable = new DataTable(); // 获取公共实例属性(排除索引器) PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(prop => prop.GetIndexParameters().Length == 0 && prop.CanRead) .ToArray(); // 创建列 foreach (PropertyInfo prop in properties) { Type columnType = prop.PropertyType; // 处理Nullable类 if (columnType.IsGenericType && columnType.GetGenericTypeDefinition() == typeof(Nullable<>)) { columnType = Nullable.GetUnderlyingType(columnType) ?? columnType; } dataTable.Columns.Add(prop.Name, columnType); } foreach (T item in collection) { DataRow row = dataTable.NewRow(); foreach (PropertyInfo prop in properties) { object value = prop.GetValue(item, null); row[prop.Name] = value == null ? DBNull.Value : value; } dataTable.Rows.Add(row); } return dataTable; } ``` 这样,代码更加健壮。 总结:我们提供了一个通用的扩展方法,可以将BindingList<T>(或任何IEnumerable<T>)换为DataTable,并处理了可空类、空值和索引器问题。 如果用户使用的是.NET Framework 3.5及以上,可以使用Linq(需要引入System.Linq),所以我们在获取属性时使用Where筛选。如果使用低版本,则可以使用循环来过滤。 现在,我们可以生成相关问题了。</think>### 将 BindingList 换为 DataTable 的实现方法 在 C# 中,将 `BindingList<T>` 换为 `DataTable` 可以通过反射动态获取对象属性并构建数据表结构。以下是完整实现: ```csharp using System; using System.ComponentModel; using System.Data; using System.Reflection; public static DataTable ConvertToDataTable<T>(BindingList<T> bindingList) { DataTable dataTable = new DataTable(); // 获取类属性并创建列 PropertyInfo[] properties = typeof(T).GetProperties(); foreach (PropertyInfo prop in properties) { // 处理可空类 Type columnType = prop.PropertyType; if (columnType.IsGenericType && columnType.GetGenericTypeDefinition() == typeof(Nullable<>)) { columnType = Nullable.GetUnderlyingType(columnType); } dataTable.Columns.Add(prop.Name, columnType); } // 填充数据行 foreach (T item in bindingList) { DataRow row = dataTable.NewRow(); foreach (PropertyInfo prop in properties) { object value = prop.GetValue(item, null); row[prop.Name] = value ?? DBNull.Value; // 处理空值 } dataTable.Rows.Add(row); } return dataTable; } ``` #### 使用示例: ```csharp // 定义实体类 public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } } // 换操作 BindingList<Product> products = new BindingList<Product> { new Product { Id = 1, Name = "Laptop", Price = 1200.50m }, new Product { Id = 2, Name = "Mouse", Price = 25.99m } }; DataTable productTable = ConvertToDataTable(products); ``` #### 关键点说明: 1. **动态列创建**:通过反射获取 `T` 的所有属性,自动创建对应的 `DataTable` 列 2. **空值处理**:使用 `DBNull.Value` 替代 `null`,避免数据表出现空引用 3. **可空类支持**:特殊处理 `Nullable<T>` 类,提取基础类作为列类 4. **类安全**:保留原始数据类(如 `int`、`decimal` 等) 5. **高效遍历**:直接操作 `BindingList` 的枚举接口,避免额外换 #### 性能优化建议: 对于大数据量场景: ```csharp // 在循环外缓存属性数组 var props = typeof(T).GetProperties(); // 使用索引器替代反射(需实现接口) public interface IDataRowConvertible { void FillDataRow(DataRow row); } // 在实体类中实现 public class Product : IDataRowConvertible { public void FillDataRow(DataRow row) { row["Id"] = Id; row["Name"] = Name; // ... } } ``` #### 注意事项: 1. 复杂类(如嵌套类)需要特殊处理 2. 只读属性会被包含在换结果中 3. 建议添加错误处理(如属性访问异常) 4. 使用 `BindingList` 时注意线程安全问题[^1]
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值