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

基于Dapper的多模型CRUD:实现MyMapper运行时动态返回对应类型

解决方案:动态处理Dapper的多模型CRUD

Great question—handling dynamic model types with Dapper for a large set of tables (40 in your case) is a super common scenario when building flexible data access layers. Let’s break down the most practical approaches tailored to your setup:


方案1:直接利用Dapper的隐式类型识别(最简单首选)

You might not even need fancy reflection here! Dapper’s CRUD methods (like Insert) automatically use the runtime type of the object you pass in, not just the compile-time object type.

怎么做:

  1. 确保你的MyMapper方法返回对应模型的实际实例(比如传入modelName="User"时,返回User类型对象),哪怕方法签名是object。比如你的映射逻辑应该先通过Activator.CreateInstance(modelType)创建目标模型实例,再填充属性,而不是返回泛型object包装。
  2. 直接将映射后的对象传给Dapper的Insert方法即可,无需强制类型转换:
// 初始化一次的静态缓存,避免重复查找模型类型
private static readonly Dictionary<string, Type> _modelTypeMap = new(StringComparer.OrdinalIgnoreCase)
{
    {"User", typeof(User)},
    {"Product", typeof(Product)},
    // ... 其他38个模型的映射
};

public async Task<int> InsertAsync(string modelName, object myModelObj, IDbConnection connection)
{
    var mappedObj = MyMapper(modelName, myModelObj);
    // Dapper会自动识别mappedObj的实际运行时类型,生成对应表的插入SQL
    return await connection.InsertAsync(mappedObj);
}

优点:

  • 零额外复杂代码,最直接高效
  • Dapper会缓存每个类型的SQL生成逻辑,40个模型也能保证性能
  • 无反射开销

注意点:

  • 务必确认MyMapper创建的是目标模型的真实实例,而非匿名类型或泛型object包装。如果当前映射逻辑只是把属性绑定到object,需要调整为先实例化目标类型再赋值。

方案2:反射调用Dapper的泛型Insert方法(显式类型控制)

如果无法调整MyMapper返回实际模型实例,或者需要显式控制SQL生成的类型,可以用反射动态调用Dapper的泛型Insert<T>方法。

怎么做:

  1. 缓存模型类型映射和Dapper的泛型方法定义,避免重复反射操作:
// 缓存模型类型映射
private static readonly Dictionary<string, Type> _modelTypeMap = new(StringComparer.OrdinalIgnoreCase)
{
    {"User", typeof(User)},
    // ... 其他模型
};

// 缓存Dapper的InsertAsync泛型方法定义(匹配正确的重载)
private static readonly MethodInfo _dapperInsertGenericMethod = typeof(SqlMapper)
    .GetMethods(BindingFlags.Public | BindingFlags.Static)
    .First(m => m.Name == "InsertAsync" && m.GetParameters().Length == 1 && m.IsGenericMethodDefinition);
  1. 动态构造并调用泛型方法:
public async Task<int> InsertAsync(string modelName, object myModelObj, IDbConnection connection)
{
    if (!_modelTypeMap.TryGetValue(modelName, out var modelType))
        throw new ArgumentException($"Unknown model: {modelName}");

    var mappedObj = MyMapper(modelName, myModelObj);
    // 构造对应模型类型的泛型Insert方法
    var genericInsertMethod = _dapperInsertGenericMethod.MakeGenericMethod(modelType);
    // 调用异步方法并转换为Task<int>
    var task = (Task<int>)genericInsertMethod.Invoke(null, new object[] { connection, mappedObj });
    return await task;
}

优点:

  • 无需修改MyMapper的返回类型,只要能获取目标模型的Type即可
  • 对SQL生成的类型有完全的显式控制权

注意点:

  • 反射有轻微性能损耗,但通过缓存方法定义和类型映射可以消除大部分开销
  • 确保匹配正确的Dapper方法重载(如果需要传入事务、命令超时等参数,要调整GetMethods的过滤条件)

方案3:泛型Mapper工厂(类型安全,可扩展)

如果想转向更类型安全的架构,避免长期依赖反射,可以将映射逻辑重构为泛型接口,通过工厂模式在运行时解析对应的映射器。

怎么做:

  1. 定义泛型映射器接口:
public interface IModelMapper<T> where T : class, new()
{
    T Map(object input);
}
  1. 为每个模型实现对应的映射器(比如UserMapper):
public class UserMapper : IModelMapper<User>
{
    public User Map(object input)
    {
        // 实现User模型的具体映射逻辑
        var inputObj = input as MyInputType;
        return new User { Id = inputObj.Id, Name = inputObj.Name };
    }
}
  1. 创建映射器工厂,通过模型名称解析对应的映射器:
public class MapperFactory
{
    private readonly Dictionary<string, object> _mappers = new(StringComparer.OrdinalIgnoreCase)
    {
        {"User", new UserMapper()},
        // ... 其他模型的映射器实例
    };

    public IModelMapper<T> GetMapper<T>(string modelName) where T : class, new()
    {
        if (_mappers.TryGetValue(modelName, out var mapper))
            return mapper as IModelMapper<T>;
        throw new ArgumentException($"No mapper found for {modelName}");
    }
}
  1. 结合反射动态调用泛型方法完成映射和插入:
public async Task<int> InsertAsync(string modelName, object myModelObj, IDbConnection connection)
{
    var mapperFactory = new MapperFactory();
    var modelType = _modelTypeMap[modelName];
    
    // 动态获取对应模型的映射器
    var getMapperMethod = typeof(MapperFactory).GetMethod("GetMapper").MakeGenericMethod(modelType);
    var mapper = getMapperMethod.Invoke(mapperFactory, new object[] { modelName });
    
    // 调用映射方法得到目标模型实例
    var mapMethod = modelType.GetMethod("Map");
    var mappedObj = mapMethod.Invoke(mapper, new object[] { myModelObj });
    
    // 调用Dapper的泛型Insert方法
    var insertMethod = _dapperInsertGenericMethod.MakeGenericMethod(modelType);
    var task = (Task<int>)insertMethod.Invoke(null, new object[] { connection, mappedObj });
    return await task;
}

优点:

  • 单个映射器完全类型安全,映射逻辑有编译时检查
  • 扩展性强,新增模型只需添加对应的IModelMapper<T>实现
  • 关注点分离清晰

注意点:

  • 前期需要编写较多代码(每个模型一个映射器)
  • 仍需少量反射来动态解析映射器,也可以用Autofac等DI容器替代反射完成解析

最终建议

优先选择方案1,它是最简单、性能最优的方案。只有在无法调整MyMapper返回实际模型实例,或者有特殊类型控制需求时,再考虑反射或工厂模式。

内容的提问来源于stack exchange,提问作者Didier Jean Charles

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:42:31