基于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.
怎么做:
- 确保你的
MyMapper方法返回对应模型的实际实例(比如传入modelName="User"时,返回User类型对象),哪怕方法签名是object。比如你的映射逻辑应该先通过Activator.CreateInstance(modelType)创建目标模型实例,再填充属性,而不是返回泛型object包装。 - 直接将映射后的对象传给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>方法。
怎么做:
- 缓存模型类型映射和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);
- 动态构造并调用泛型方法:
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工厂(类型安全,可扩展)
如果想转向更类型安全的架构,避免长期依赖反射,可以将映射逻辑重构为泛型接口,通过工厂模式在运行时解析对应的映射器。
怎么做:
- 定义泛型映射器接口:
public interface IModelMapper<T> where T : class, new() { T Map(object input); }
- 为每个模型实现对应的映射器(比如
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 }; } }
- 创建映射器工厂,通过模型名称解析对应的映射器:
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}"); } }
- 结合反射动态调用泛型方法完成映射和插入:
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
相关产品推荐
相关产品推荐

