You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

不使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 20:25:59