C# 使用泛型委托与反射将 DataTable 高效转换为实体集合
在 .NET 开发中,将数据库查询结果(如 DataSet/DataTable)转换为业务实体对象是常见需求。传统的反射直接赋值往往需要处理繁琐的类型转换问题,尤其是当属性类型为整数或布尔值时,直接调用 Set 方法容易抛出异常。本文将演示如何利用 C# 的泛型委托(Generic Delegate)与反射机制,动态创建匹配的 Setter 代理,从而优雅地解决多类型数据的自动映射难题。
实现目标与核心原理
我们的目标是编写一个通用静态方法 ToModel<T>.GetDelegate_ToModelList(DataTable),能够遍历 DataTable 的每一行,利用反射找到实体类对应属性的 Set 方法,并通过委托执行赋值操作。核心难点在于:Delegate.CreateDelegate 要求委托类型必须与方法签名严格匹配。如果委托定义为接收 string 参数,则无法赋值给 int 或 bool 类型的属性。
解决方案是利用 Type.MakeGenericType() 在运行时动态生成泛型委托类型,例如 SetValue<int> 或 SetValue<bool>,并确保传入的数据经过 Convert.ChangeType 预处理后,再通过 DynamicInvoke 进行调用。
准备工作:实体定义与依赖
假设我们有一个名为 TestModel 的实体类,包含不同数据类型:
public class TestModel
{
public int ID { get; set; }
public string Name { get; set; }
public bool IsDeleted { get; set; }
}
我们需要确保项目中引用了 System.Data、System.Reflection 和 System.Collections.Generic 命名空间。
第1步:定义泛型委托类型
首先,定义一个接受任意类型参数的泛型委托。这为我们后续动态实例化特定类型的委托奠定了基础:
public delegate void SetValue<TValue>(TValue value);
第2步:封装委托创建逻辑
创建一个辅助方法,根据属性名称获取其数据类型,并动态创建匹配的委托实例。这里使用 MakeGenericType 填充泛型参数:
/// <summary>
/// 创建指定属性 Setter 的动态委托
/// </summary>
private static Delegate CreateSetDelegate(T model, string propertyName)
{
// 1. 获取属性信息及 Set 方法
PropertyInfo propInfo = typeof(T).GetProperty(propertyName);
MethodInfo mi = propInfo.GetSetMethod();
if (mi == null) return null;
// 2. 获取属性的实际数据类型
Type propertyType = propInfo.PropertyType;
// 3. 动态构建泛型委托类型,例如 SetValue<int>
Type genericDelType = typeof(SetValue<>);
Type specificDelType = genericDelType.MakeGenericType(propertyType);
// 4. 创建委托实例
return Delegate.CreateDelegate(specificDelType, model, mi);
}
/// <summary>
/// 获取属性对应的 Type 对象
/// </summary>
private static Type GetPropertyType(string propertyName)
{
return typeof(T).GetProperty(propertyName).PropertyType;
}
第3步:实现批量转换主函数
在主转换逻辑中,遍历 DataTable,为每一行创建实体,并利用上一步创建的委托进行赋值。关键点在于使用 DynamicInvoke 代替强类型委托调用,以便传递经过类型转换后的参数:
public static IList<T> GetDelegate_ToModelList(DataTable dt)
{
IList<T> list = new List<T>;
if (dt == null || dt.Rows.Count < 1)
return list;
foreach (DataRow dr in dt.Rows)
{
T model = new T();
foreach (DataColumn dc in dt.Columns)
{
string propName = dc.ColumnName;
// 获取该列对应的委托
Delegate setDelegate = CreateSetDelegate(model, propName);
if (setDelegate != null)
{
// 核心步骤:将 DataRow 中的值转换为目标属性所需的类型,然后动态调用
object convertedValue = Convert.ChangeType(dr[dc.ColumnName], GetPropertyType(propName));
try
{
setDelegate.DynamicInvoke(convertedValue);
}
catch (TargetInvocationException ex)
{
// 在实际生产环境中,建议记录日志而非静默失败
// 通常因属性不可写或类型不兼容导致
Console.WriteLine($"Failed to set {propName}: {ex.InnerException?.Message}");
}
}
}
list.Add(model);
}
return list;
}
第4步:测试验证
构建一个简单的测试用例,模拟从数据库返回的表结构:
static DataTable BuildSampleTable()
{
DataTable result = new DataTable();
result.Columns.Add("ID", typeof(int));
result.Columns.Add("Name", typeof(string));
result.Columns.Add("IsDeleted", typeof(bool));
result.Rows.Add(new object[] { 1, "Alice", false });
result.Rows.Add(new object[] { 2, "Bob", true });
return result;
}
// 调用示例
var models = ToModel<TestModel>.GetDelegate_ToModelList(BuildSampleTable());
foreach (var m in models)
{
Console.WriteLine(m.ToString());
}
性能考量与总结
此方案通过 Delegate.DynamicInvoke 解决了多类型赋值问题,同时利用 Cache 机制(本例中未展示缓存,但实际应用中建议对 CreateSetDelegate 的结果进行字典缓存)可以极大提升性能。相比于每次解析 Expression Tree 或直接 SetValue 反射,这种方式代码更清晰,且利用了委托调用的相对高性能特性。
以上就是利用委托反射将DataTable转换为实体集的详细内容,更多关于C#数据映射技巧的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













