如何在ADO.NET包装类中返回实体类列表而非DataTable?
嘿,这个需求其实很常见,咱们可以通过泛型+反射的方式来实现,不用引入复杂的ORM,完全贴合你简化ADO.NET代码的目标。我给你一步步拆解解决方案:
首先先修正一个小细节:你原来的ParametersGroup里paramValue是string类型,这会限制参数类型(比如数字、日期没法直接传),建议改成object类型,兼容性更好:
public class ParametersGroup { public string paramName { get; set; } public object paramValue { get; set; } // 改为object类型支持多类型参数 }
核心实现:泛型查询+实体映射
我们要把ExecuteQuery改成泛型方法,让它能返回任意实体类型的列表,同时实现一个辅助方法把DataRow的数据自动映射到实体属性上。
1. 改造ExecuteQuery为泛型方法
替换原来返回DataTable的方法,改成返回List<T>,并添加where T : new()约束(确保能创建实体类实例):
internal class adoWrapper { public string _connectionString { get; set; } private static readonly Dictionary<Type, PropertyInfo[]> _propertyCache = new Dictionary<Type, PropertyInfo[]>(); public adoWrapper(string connectionString) { _connectionString = connectionString; } // 你的其他方法(ExecuteNonQuery、ExecuteScalar)保持不变... public List<T> ExecuteQuery<T>(string query, List<ParametersGroup> parameters) where T : new() { if (string.IsNullOrEmpty(query)) { throw new ArgumentException("Query cannot be null or empty", nameof(query)); // 用ArgumentException替代NullReferenceException,更符合.NET异常规范 } var resultList = new List<T>(); DataTable dt = new DataTable(); using (SqlConnection con = new SqlConnection(_connectionString)) using (SqlCommand cmd = new SqlCommand(query, con)) { if (parameters != null && parameters.Count > 0) { foreach (var param in parameters) { // 处理null值,避免传入null导致数据库报错 cmd.Parameters.AddWithValue(param.paramName, param.paramValue ?? DBNull.Value); } } cmd.CommandType = CommandType.Text; con.Open(); using (SqlDataAdapter ad = new SqlDataAdapter(cmd)) { ad.Fill(dt); } } // 将DataTable每行数据映射为实体 foreach (DataRow row in dt.Rows) { var entity = MapDataRowToEntity<T>(row); resultList.Add(entity); } return resultList; } }
2. 实现DataRow到实体的映射逻辑
用反射读取实体类的属性,根据列名(和属性名对应)自动赋值,同时处理类型转换:
private T MapDataRowToEntity<T>(DataRow row) where T : new() { var entity = new T(); var entityType = typeof(T); // 缓存实体属性,避免重复反射提升性能 if (!_propertyCache.TryGetValue(entityType, out var properties)) { properties = entityType.GetProperties(BindingFlags.Public | BindingFlags.Instance); _propertyCache[entityType] = properties; } foreach (var prop in properties) { // 忽略大小写匹配列名,更灵活 var columnName = row.Table.Columns.Cast<DataColumn>() .FirstOrDefault(c => c.Name.Equals(prop.Name, StringComparison.OrdinalIgnoreCase))?.Name; if (columnName != null && !row.IsNull(columnName)) { var columnValue = row[columnName]; // 处理类型转换,适配数据库类型与实体属性类型差异 var convertedValue = Convert.ChangeType(columnValue, prop.PropertyType); prop.SetValue(entity, convertedValue); } } return entity; }
使用示例
假设你有一个TableA实体类:
public class TableA { public int Id { get; set; } public string UserName { get; set; } public DateTime CreateTime { get; set; } }
调用时直接指定泛型类型即可:
var dbWrapper = new adoWrapper("your_connection_string"); var parameters = new List<ParametersGroup> { new ParametersGroup { paramName = "@Id", paramValue = 1001 } }; // 直接获取实体列表 List<TableA> tableAData = dbWrapper.ExecuteQuery<TableA>( "SELECT Id, UserName, CreateTime FROM TableA WHERE Id = @Id", parameters );
额外说明
- 这个方案完全基于你现有的ADO.NET包装类,没有引入任何ORM依赖,符合你“简化代码但不用ORM”的需求。
- 缓存反射属性的逻辑可以避免重复反射带来的性能损耗,对于高频调用场景很有用。
- 如果需要更复杂的映射规则(比如列名和属性名不一致),可以给实体属性加自定义特性(比如
[Column("DbColumnName")]),然后在映射逻辑里读取特性值来匹配列名。
内容的提问来源于stack exchange,提问作者Tanwer
相关产品推荐
相关产品推荐

