如何在Dapper中优雅处理派生类型的查询操作?
如何在Dapper中优雅处理派生类型的查询操作?
我完全理解你的困扰——既然删除操作能通过反射动态调用对应存储过程,查询操作却要写一堆switch分支,确实不够优雅。核心问题在于Dapper的QuerySingle<T>是泛型方法,需要在编译时明确类型,但你现在只有运行时的Type对象。不过我们可以通过反射来绕开这个限制,把重复的switch逻辑替换成动态调用的代码。
解决方案一:直接反射调用Dapper的泛型查询方法
我们可以通过反射获取Dapper的QuerySingle泛型方法,再动态创建对应实体类型的方法实例并调用,这样就能彻底去掉switch分支:
public static ModelBase ModelGet(Type t, int id, SqlConnection? conn = null, SqlTransaction? trans = null, bool withDependents = false) { bool connectionIsNew = (conn is null); bool transactionIsNew = (trans is null); ModelBase model = default!; try { if (connectionIsNew) conn = new SqlConnection(Globals.connString); if (conn is null) { throw new Exception("Unable to establish a database connection in Data.ModelGet"); } else { if (conn.State != System.Data.ConnectionState.Open) conn.Open(); } if (transactionIsNew) trans = conn.BeginTransaction(); var p = new { Id = id }; string spName = $"sp{t.Name}_Get"; // 反射获取Dapper的QuerySingle泛型方法 var querySingleMethod = typeof(SqlConnectionExtensions) .GetMethod("QuerySingle", new[] { typeof(SqlConnection), typeof(string), typeof(object), typeof(SqlTransaction), typeof(int?), typeof(CommandType?) }) ?.MakeGenericMethod(t); if (querySingleMethod == null) { throw new InvalidOperationException("无法找到Dapper的QuerySingle泛型方法"); } // 调用动态生成的泛型方法 object? result = querySingleMethod.Invoke( null, new object?[] { conn, spName, p, trans, null, CommandType.StoredProcedure } ); model = result as ModelBase ?? throw new InvalidCastException( $"存储过程返回的对象无法转换为ModelBase类型" ); } catch (Exception exc) { // 不要留空catch块,至少记录日志或重新抛出异常 throw new Exception($"查询类型 {t.Name} 时出错", exc); } finally { // 补充:处理事务提交/回滚和连接关闭,避免资源泄漏 if (transactionIsNew && trans != null) { try { trans.Commit(); } catch { trans.Rollback(); throw; } } if (connectionIsNew && conn != null && conn.State == System.Data.ConnectionState.Open) { conn.Close(); } } return model; }
解决方案二:封装泛型辅助方法后反射调用
如果觉得直接调用Dapper的方法太繁琐,可以先封装一个泛型内部方法,再通过反射调用它,代码可读性会更好:
// 内部泛型辅助方法 private static T ModelGetInternal<T>(int id, SqlConnection conn, SqlTransaction trans) where T : ModelBase { var p = new { Id = id }; string spName = $"sp{typeof(T).Name}_Get"; return conn.QuerySingle<T>(spName, p, trans, commandType: CommandType.StoredProcedure); } // 改造后的公共方法 public static ModelBase ModelGet(Type t, int id, SqlConnection? conn = null, SqlTransaction? trans = null, bool withDependents = false) { // (连接、事务的初始化逻辑和方案一一致,此处省略) // 替换switch分支:反射调用内部泛型方法 var getMethod = typeof(YourDataClass) // 替换成你的实际类名 .GetMethod("ModelGetInternal", BindingFlags.NonPublic | BindingFlags.Static) ?.MakeGenericMethod(t); if (getMethod == null) { throw new InvalidOperationException("无法找到内部的ModelGetInternal泛型方法"); } object? result = getMethod.Invoke(null, new object[] { id, conn!, trans! }); model = result as ModelBase ?? throw new InvalidCastException( $"存储过程返回的对象无法转换为ModelBase类型" ); // (事务、连接的清理逻辑和方案一一致,此处省略) return model; }
关键注意事项
- 缓存提升性能:如果这个查询方法会被频繁调用,建议用
Dictionary<Type, MethodInfo>缓存生成好的泛型方法实例,避免每次都反射查找方法,减少性能开销。 - 异常处理优化:你原来的代码里存在空catch块、throw后写return(死代码)等问题,这些都会隐藏错误或导致逻辑异常,一定要修正。
- 资源泄漏防范:务必在finally块中处理事务的提交/回滚和数据库连接的关闭,避免长期占用数据库资源。
- 类型映射匹配:确保每个实体类对应的存储过程返回字段,和类的属性名称、类型完全匹配,否则Dapper会无法正确映射对象。
- 空值场景处理:如果存储过程可能返回null(比如找不到对应Id的记录),建议用
QuerySingleOrDefault代替QuerySingle,并提前处理null返回的情况。
备注:内容来源于stack exchange,提问作者mdicoope
相关产品推荐
相关产品推荐

