不使用Entity Framework,如何将数据库表行映射为C#对象?
问题描述
我有如下C#模型:
public class ExampleModel { public string A {get; set;} public string B {get; set;} //... }
以及用于获取数据库表行的SQL代码:
SELECT a, b, ... FROM example_table;
我希望将该SQL查询的结果映射为ExampleModel列表。目前我采用的方式如下:
using (DbCommand cmd = _conn.CreateCommand()) { string sql = "SELECT a, b, ... FROM example_table;"; cmd.CommandType = System.Data.CommandType.Text; cmd.CommandText = sql; using(DbDataReader reader = cmd.ExecuteReader()) { List<ExampleModel> exampleModelList = new(); while(reader.Read()) { ExampleModel exampleModel = new ExampleModel(); exampleModel.A = (string)reader["a"]; exampleModel.B = (string)reader["b"]; //... exampleModelList.Add(exampleModel); } } }
假设数据库表包含大量列,请问在不手动映射表行数据到C#对象的前提下,有没有更快捷的实现方式?注意:我无法使用Entity Framework。
解决方案
1. 反射实现通用映射
可以编写通用扩展方法,通过反射自动匹配数据库列名与C#属性名(默认处理大小写不敏感匹配):
public static class DataReaderExtensions { public static List<T> MapToList<T>(this DbDataReader reader) where T : new() { var resultList = new List<T>(); var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); while (reader.Read()) { var item = new T(); foreach (var prop in properties) { var columnName = prop.Name.ToLower(); if (reader.HasColumn(columnName) && !reader.IsDBNull(reader.GetOrdinal(columnName))) { var value = reader[columnName]; prop.SetValue(item, value); } } resultList.Add(item); } return resultList; } private static bool HasColumn(this DbDataReader reader, string columnName) { for (int i = 0; i < reader.FieldCount; i++) { if (reader.GetName(i).Equals(columnName, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } }
使用时直接调用扩展方法:
using (DbCommand cmd = _conn.CreateCommand()) { string sql = "SELECT a, b, ... FROM example_table;"; cmd.CommandType = System.Data.CommandType.Text; cmd.CommandText = sql; using(DbDataReader reader = cmd.ExecuteReader()) { List<ExampleModel> exampleModelList = reader.MapToList<ExampleModel>(); } }
2. 轻量级ORM(如Dapper)
虽然不能用Entity Framework,但可以用轻量级Micro ORM——Dapper,它专注数据映射,无EF的复杂特性,性能接近手动映射且使用简单。
先通过NuGet安装Dapper,再修改代码:
using Dapper; // ... using (DbCommand cmd = _conn.CreateCommand()) { string sql = "SELECT a, b, ... FROM example_table;"; cmd.CommandType = System.Data.CommandType.Text; cmd.CommandText = sql; List<ExampleModel> exampleModelList = _conn.Query<ExampleModel>(sql).ToList(); }
Dapper会自动处理列名与属性名的大小写匹配,还能完成基础类型转换,比手写反射更高效稳定。
3. Emit动态映射(高性能场景)
如果追求极致性能,可使用System.Reflection.Emit生成动态映射方法,避免反射的性能开销,适合高频调用场景:
public static class DynamicMapper { private static readonly Dictionary<Type, Func<DbDataReader, object>> _mappers = new(); public static List<T> MapToList<T>(DbDataReader reader) where T : new() { var list = new List<T>(); var mapper = GetMapper<T>(); while (reader.Read()) { list.Add((T)mapper(reader)); } return list; } private static Func<DbDataReader, object> GetMapper<T>() where T : new() { var type = typeof(T); if (_mappers.TryGetValue(type, out var mapper)) { return mapper; } var method = new DynamicMethod("MapTo" + type.Name, type, new[] { typeof(DbDataReader) }, type.Module); var il = method.GetILGenerator(); // 创建实例 il.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes)); var local = il.DeclareLocal(type); il.Emit(OpCodes.Stloc, local); var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in properties) { var columnName = prop.Name.ToLower(); // 获取列索引 il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldstr, columnName); il.Emit(OpCodes.Callvirt, typeof(DbDataReader).GetMethod("GetOrdinal", new[] { typeof(string) })); // 检查是否为DBNull il.Emit(OpCodes.Dup); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Callvirt, typeof(DbDataReader).GetMethod("IsDBNull", new[] { typeof(int) })); var skip = il.DefineLabel(); il.Emit(OpCodes.Brtrue, skip); // 读取列值并设置属性 var getMethod = typeof(DbDataReader).GetMethod("GetFieldValue", new[] { typeof(int) }).MakeGenericMethod(prop.PropertyType); il.Emit(OpCodes.Callvirt, getMethod); il.Emit(OpCodes.Ldloc, local); il.Emit(OpCodes.Swap); il.Emit(OpCodes.Callvirt, prop.GetSetMethod()); il.MarkLabel(skip); il.Emit(OpCodes.Pop); // 弹出冗余的列索引 } il.Emit(OpCodes.Ldloc, local); il.Emit(OpCodes.Ret); mapper = (Func<DbDataReader, object>)method.CreateDelegate(typeof(Func<DbDataReader, object>)); _mappers[type] = mapper; return mapper; } }
使用方式:
using(DbDataReader reader = cmd.ExecuteReader()) { List<ExampleModel> exampleModelList = DynamicMapper.MapToList<ExampleModel>(reader); }
内容的提问来源于stack exchange,提问作者Librapulpfiction
相关产品推荐
相关产品推荐

