|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// 【文件说明】HwPortalMyBatisExecutor.cs - MyBatis 风格 SQL 执行器
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// 这个类是"类 MyBatis"架构的核心执行器,负责把 XML 中定义的 SQL 转换为真实的数据库操作。
|
|
|
|
|
|
//
|
|
|
|
|
|
// 【架构定位】
|
|
|
|
|
|
// 在 Java MyBatis 中:
|
|
|
|
|
|
// SqlSession sqlSession = sqlSessionFactory.openSession();
|
|
|
|
|
|
// UserMapper mapper = sqlSession.getMapper(UserMapper.class);
|
|
|
|
|
|
// User user = mapper.selectById(1L);
|
|
|
|
|
|
//
|
|
|
|
|
|
// 在这个 C# 实现中:
|
|
|
|
|
|
// HwPortalMyBatisExecutor executor = ...; // 由 DI 容器注入
|
|
|
|
|
|
// User user = await executor.QuerySingleAsync<User>("UserMapper", "selectById", new { id = 1L });
|
|
|
|
|
|
//
|
|
|
|
|
|
// 两者区别:
|
|
|
|
|
|
// - Java MyBatis 用动态代理生成 Mapper 接口的实现
|
|
|
|
|
|
// - 这里用执行器模式,直接通过字符串名称调用,更简单但不够类型安全
|
|
|
|
|
|
//
|
|
|
|
|
|
// 【为什么需要这个类?】
|
|
|
|
|
|
// 1. 兼容迁移:从 Java 迁移过来的项目有大量 XML SQL 定义
|
|
|
|
|
|
// 2. 复杂 SQL:有些 SQL 用 Lambda/LINQ 很难表达,原生 SQL 更直接
|
|
|
|
|
|
// 3. 动态条件:MyBatis 的 <if>、<where>、<foreach> 标签支持动态 SQL 组装
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
namespace Admin.NET.Plugin.HwPortal;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// MyBatis 风格 SQL 执行器。
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// 【C# 语法知识点 - sealed 密封类】
|
|
|
|
|
|
/// sealed 关键字表示"密封类",不能被继承。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 为什么要密封?
|
|
|
|
|
|
/// 1. 安全性:防止子类篡改核心逻辑
|
|
|
|
|
|
/// 2. 性能:编译器可以对密封类做优化(如方法内联)
|
|
|
|
|
|
/// 3. 设计意图:这个类就是最终实现,不需要扩展
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 对比 Java:
|
|
|
|
|
|
/// Java 用 final 关键字:public final class MyBatisExecutor { ... }
|
|
|
|
|
|
/// C# 用 sealed 关键字:public sealed class HwPortalMyBatisExecutor { ... }
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// 【依赖注入设计】
|
|
|
|
|
|
/// 构造函数接收两个依赖:
|
|
|
|
|
|
/// - ISqlSugarClient:SqlSugar ORM 的数据库客户端
|
|
|
|
|
|
/// - HwPortalMapperRegistry:XML Mapper 的注册表(负责解析 SQL)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 这是"依赖倒置原则"的体现:高层模块(执行器)不依赖低层模块(具体数据库实现),
|
|
|
|
|
|
/// 而是依赖抽象(接口)。
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public sealed class HwPortalMyBatisExecutor
|
|
|
|
|
|
{
|
|
|
|
|
|
// 【C# 语法知识点 - readonly 字段】
|
|
|
|
|
|
// readonly 表示"只读字段",只能在构造函数中赋值,之后不能修改。
|
|
|
|
|
|
//
|
|
|
|
|
|
// 为什么用 readonly?
|
|
|
|
|
|
// 1. 防止意外修改:数据库客户端不应该被替换
|
|
|
|
|
|
// 2. 线程安全:readonly 字段天然线程安全
|
|
|
|
|
|
// 3. 依赖注入的最佳实践:注入的依赖通常都应该是 readonly
|
|
|
|
|
|
//
|
|
|
|
|
|
// 对比 Java:
|
|
|
|
|
|
// Java 用 final 关键字:private final SqlSession sqlSession;
|
|
|
|
|
|
// C# 用 readonly 关键字:private readonly ISqlSugarClient _db;
|
|
|
|
|
|
//
|
|
|
|
|
|
// ISqlSugarClient 是 SqlSugar ORM 的数据库客户端抽象。
|
|
|
|
|
|
private readonly ISqlSugarClient _db;
|
|
|
|
|
|
|
|
|
|
|
|
// 【命名约定】
|
|
|
|
|
|
// _registry 以下划线开头,表示私有字段。
|
|
|
|
|
|
// 这是 C# 的常见命名规范(尤其是依赖注入的字段)。
|
|
|
|
|
|
//
|
|
|
|
|
|
// Registry 是"注册表"模式:
|
|
|
|
|
|
// - 负责把 XML 中定义的 SQL 语句解析成可执行的 SQL
|
|
|
|
|
|
// - 缓存解析结果,避免每次执行都重新解析 XML
|
|
|
|
|
|
// - 处理参数替换(#{paramName} -> @paramName)
|
|
|
|
|
|
private readonly HwPortalMapperRegistry _registry;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 构造函数(依赖注入)。
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// 【C# 语法知识点 - 构造函数】
|
|
|
|
|
|
/// 构造函数是创建对象时自动调用的特殊方法,负责初始化对象状态。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 语法特点:
|
|
|
|
|
|
/// - 方法名必须与类名相同
|
|
|
|
|
|
/// - 没有返回值(连 void 都不写)
|
|
|
|
|
|
/// - 可以重载(多个参数不同的构造函数)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 对比 Java:
|
|
|
|
|
|
/// Java 的构造函数语法几乎一样:
|
|
|
|
|
|
/// public HwPortalMyBatisExecutor(ISqlSugarClient db, HwPortalMapperRegistry registry) {
|
|
|
|
|
|
/// this.db = db;
|
|
|
|
|
|
/// this.registry = registry;
|
|
|
|
|
|
/// }
|
|
|
|
|
|
///
|
|
|
|
|
|
/// C# 的区别:
|
|
|
|
|
|
/// - 可以用 this 简化:this._db = db;(但 C# 更常用 _db = db; 省略 this)
|
|
|
|
|
|
/// - Java 习惯用 this.db = db; 区分成员变量和参数
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// 【依赖注入容器的工作流程】
|
|
|
|
|
|
/// 1. 应用启动时,Startup.cs 中注册服务:
|
|
|
|
|
|
/// services.AddScoped<HwPortalMyBatisExecutor>();
|
|
|
|
|
|
/// services.AddSingleton<HwPortalMapperRegistry>();
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 2. 当某个类(如 HwWebService)需要 HwPortalMyBatisExecutor 时:
|
|
|
|
|
|
/// public HwWebService(HwPortalMyBatisExecutor executor) { ... }
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 3. DI 容器检查 HwPortalMyBatisExecutor 的构造函数需要什么:
|
|
|
|
|
|
/// - 需要 ISqlSugarClient -> 容器创建/获取实例
|
|
|
|
|
|
/// - 需要 HwPortalMapperRegistry -> 容器创建/获取实例(因为是 Singleton,用已有的)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 4. 容器调用这个构造函数,传入所需的依赖
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 5. 返回创建好的 HwPortalMyBatisExecutor 实例给请求者
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 整个过程是"递归解析依赖":A 依赖 B,B 依赖 C,容器会自动按顺序创建 C->B->A。
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="db">SqlSugar 数据库客户端(由 DI 容器提供)</param>
|
|
|
|
|
|
/// <param name="registry">Mapper 注册表(由 DI 容器提供)</param>
|
|
|
|
|
|
public HwPortalMyBatisExecutor(ISqlSugarClient db, HwPortalMapperRegistry registry)
|
|
|
|
|
|
{
|
|
|
|
|
|
_db = db;
|
|
|
|
|
|
_registry = registry;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 查询列表(返回多条记录)。
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// 【C# 语法知识点 - 泛型方法 <T>】
|
|
|
|
|
|
/// public Task<List<T>> QueryListAsync<T>(...)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 这里的 <T> 是泛型参数,表示"这个方法可以处理任何类型"。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 调用示例:
|
|
|
|
|
|
/// List<HwProductInfo> products = await executor.QueryListAsync<HwProductInfo>(
|
|
|
|
|
|
/// "HwProductInfoMapper",
|
|
|
|
|
|
/// "selectList",
|
|
|
|
|
|
/// new { categoryId = 1 }
|
|
|
|
|
|
/// );
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 类型推断简化:
|
|
|
|
|
|
/// var products = await executor.QueryListAsync<HwProductInfo>(...);
|
|
|
|
|
|
/// // 或者直接:
|
|
|
|
|
|
/// List<HwProductInfo> products = await executor.QueryListAsync(...);
|
|
|
|
|
|
/// // 编译器会根据赋值目标推断 T 是 HwProductInfo
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 对比 Java:
|
|
|
|
|
|
/// Java 的泛型方法写法:
|
|
|
|
|
|
/// public <T> List<T> queryList(String mapperName, String statementId, Object parameter) { ... }
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 关键差异:
|
|
|
|
|
|
/// - Java 的泛型有"类型擦除",运行时 List<T> 只是 List,T 变成了 Object
|
|
|
|
|
|
/// - C# 的泛型是"真实泛型",运行时 T 就是具体的类型,性能更好
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// 【C# 语法知识点 - 默认参数值】
|
|
|
|
|
|
/// object parameter = null 表示参数有默认值 null。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 调用时可以省略:
|
|
|
|
|
|
/// QueryListAsync<HwProductInfo>("Mapper", "selectAll"); // parameter 自动为 null
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 对比 Java:
|
|
|
|
|
|
/// Java 不支持默认参数值,需要方法重载:
|
|
|
|
|
|
/// public <T> List<T> queryList(String mapperName, String statementId) {
|
|
|
|
|
|
/// return queryList(mapperName, statementId, null);
|
|
|
|
|
|
/// }
|
|
|
|
|
|
/// public <T> List<T> queryList(String mapperName, String statementId, Object parameter) { ... }
|
|
|
|
|
|
///
|
|
|
|
|
|
/// C# 的默认参数更简洁,减少代码重复。
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// 【C# 语法知识点 - Task<T> 异步返回类型】
|
|
|
|
|
|
/// Task<List<T>> 表示"一个异步操作,最终会返回 List<T>"。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 这是 .NET 异步编程的基础:
|
|
|
|
|
|
/// - Task:表示一个异步操作(不返回值的异步方法用 Task)
|
|
|
|
|
|
/// - Task<T>:表示返回 T 类型的异步操作
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 对比 Java:
|
|
|
|
|
|
/// Java 的异步返回类型:
|
|
|
|
|
|
/// - CompletableFuture<T>(Java 8+)
|
|
|
|
|
|
/// - ListenableFuture<T>(Guava)
|
|
|
|
|
|
/// - Future<T>(基础版,功能弱)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// C# 的 Task 比 Java 的 Future 功能更强大,语言级别支持 await/async。
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="T">返回的数据类型(如 HwProductInfo)</typeparam>
|
|
|
|
|
|
/// <param name="mapperName">Mapper 名称(对应 XML 文件名,如 "HwProductInfoMapper")</param>
|
|
|
|
|
|
/// <param name="statementId">SQL 语句 ID(对应 XML 中的 id,如 "selectList")</param>
|
|
|
|
|
|
/// <param name="parameter">查询参数对象(可选,null 表示无参数)</param>
|
|
|
|
|
|
/// <returns>数据列表的异步任务</returns>
|
|
|
|
|
|
public Task<List<T>> QueryListAsync<T>(string mapperName, string statementId, object parameter = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 泛型方法:
|
|
|
|
|
|
// T 表示“希望数据库结果被映射成什么类型”。
|
|
|
|
|
|
// 例如 QueryListAsync<HwProductInfo>(...) 最终会返回 List<HwProductInfo>。
|
|
|
|
|
|
HwPortalPreparedSql preparedSql = _registry.Prepare(mapperName, statementId, parameter);
|
|
|
|
|
|
return _db.Ado.SqlQueryAsync<T>(preparedSql.Sql, preparedSql.Parameters);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 查询单条记录。
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// 【方法实现分析】
|
|
|
|
|
|
/// 这个方法的实现是"先查列表再取第一条":
|
|
|
|
|
|
/// 1. 调用 QueryListAsync 获取列表
|
|
|
|
|
|
/// 2. 用 FirstOrDefault() 取第一条(或 null)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 优点:
|
|
|
|
|
|
/// - 代码复用:复用 QueryListAsync 的逻辑
|
|
|
|
|
|
/// - 简单易懂:初学者容易理解
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 缺点:
|
|
|
|
|
|
/// - 性能略差:数据库可能返回多条,浪费带宽
|
|
|
|
|
|
/// - 语义不清:LIMIT 1 应该在 SQL 层做,但这里依赖 XML 定义
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 【优化建议】
|
|
|
|
|
|
/// 生产环境应该在 XML 的 SQL 里加 LIMIT 1(MySQL)或 TOP 1(SQL Server),
|
|
|
|
|
|
/// 确保数据库只返回一条记录。
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// 【C# 语法知识点 - FirstOrDefault()】
|
|
|
|
|
|
/// FirstOrDefault() 是 LINQ 方法,返回集合的第一个元素,如果没有则返回默认值。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 对于引用类型(如 HwProductInfo),默认值是 null。
|
|
|
|
|
|
/// 对于值类型(如 int),默认值是 0。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 对比 Java:
|
|
|
|
|
|
/// Java Stream:list.stream().findFirst().orElse(null)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// C# 的 FirstOrDefault() 更简洁。
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// 【C# 语法知识点 - async/await 详解】
|
|
|
|
|
|
/// async 标记方法为异步,await 等待异步操作完成。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 执行流程:
|
|
|
|
|
|
/// 1. 调用 QueryListAsync,它返回一个 Task(尚未完成的任务)
|
|
|
|
|
|
/// 2. await 表示"暂停这个方法,等待任务完成后再继续"
|
|
|
|
|
|
/// 3. 等待期间,当前线程可以处理其他请求(不阻塞)
|
|
|
|
|
|
/// 4. 任务完成后,方法继续执行,返回结果
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 关键点:
|
|
|
|
|
|
/// - await 只能在 async 方法中使用
|
|
|
|
|
|
/// - await 不会阻塞线程,只是让出控制权
|
|
|
|
|
|
/// - 编译器会把 async/await 转换成状态机代码(类似回调,但更优雅)
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="T">返回的数据类型</typeparam>
|
|
|
|
|
|
/// <param name="mapperName">Mapper 名称</param>
|
|
|
|
|
|
/// <param name="statementId">SQL 语句 ID</param>
|
|
|
|
|
|
/// <param name="parameter">查询参数(可选)</param>
|
|
|
|
|
|
/// <returns>单条记录或 null 的异步任务</returns>
|
|
|
|
|
|
public async Task<T> QuerySingleAsync<T>(string mapperName, string statementId, object parameter = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 当前实现为了简单,先查列表再取第一条。
|
|
|
|
|
|
// 对初学者来说,这种写法比直接封装多种执行路径更好理解。
|
|
|
|
|
|
List<T> items = await QueryListAsync<T>(mapperName, statementId, parameter);
|
|
|
|
|
|
return items.FirstOrDefault();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 执行非查询 SQL(增删改)。
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// 【业务场景】
|
|
|
|
|
|
/// 用于执行 INSERT、UPDATE、DELETE 等不返回结果集的 SQL。
|
|
|
|
|
|
/// 返回"影响行数":
|
|
|
|
|
|
/// - 插入:返回插入的行数(通常是 1)
|
|
|
|
|
|
/// - 更新:返回实际修改的行数
|
|
|
|
|
|
/// - 删除:返回实际删除的行数
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 示例:
|
|
|
|
|
|
/// int rows = await executor.ExecuteAsync("UserMapper", "updateStatus", new { userId = 1, status = "active" });
|
|
|
|
|
|
/// if (rows > 0) { /* 更新成功 */ }
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// 【C# 语法知识点 - async/await 返回值】
|
|
|
|
|
|
/// return await _db.Ado.ExecuteCommandAsync(...)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 这里的 await 会:
|
|
|
|
|
|
/// 1. 等待数据库操作完成
|
|
|
|
|
|
/// 2. 解包 Task<int> 得到 int 结果
|
|
|
|
|
|
/// 3. 返回这个 int
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 如果去掉 await 直接返回 Task:
|
|
|
|
|
|
/// return _db.Ado.ExecuteCommandAsync(...); // 编译错误,类型不匹配
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 必须 await 才能拿到实际值。
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="mapperName">Mapper 名称</param>
|
|
|
|
|
|
/// <param name="statementId">SQL 语句 ID</param>
|
|
|
|
|
|
/// <param name="parameter">执行参数(可选)</param>
|
|
|
|
|
|
/// <returns>影响行数的异步任务</returns>
|
|
|
|
|
|
public async Task<int> ExecuteAsync(string mapperName, string statementId, object parameter = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
HwPortalPreparedSql preparedSql = _registry.Prepare(mapperName, statementId, parameter);
|
|
|
|
|
|
return await _db.Ado.ExecuteCommandAsync(preparedSql.Sql, preparedSql.Parameters);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 插入记录并返回自增主键。
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// 【业务场景】
|
|
|
|
|
|
/// 新增记录时,数据库会自动生成主键(自增 ID)。
|
|
|
|
|
|
/// 这个方法执行 INSERT 后,立即查询并返回生成的主键值。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 典型用法:
|
|
|
|
|
|
/// var newUser = new User { Name = "张三" }; // Id 还未知
|
|
|
|
|
|
/// long newId = await executor.InsertReturnIdentityAsync("UserMapper", "insert", newUser);
|
|
|
|
|
|
/// newUser.Id = newId; // 回填主键
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 对比 Java MyBatis:
|
|
|
|
|
|
/// Java 配置 useGeneratedKeys="true" keyProperty="id" 后,
|
|
|
|
|
|
/// MyBatis 会自动把生成的主键设置到实体的 id 属性,不需要手动查询。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 这里采用"执行 INSERT + 查询 LAST_INSERT_ID()"的方式,
|
|
|
|
|
|
/// 虽然多了一次查询,但更通用(不依赖具体 ORM 的主键回填机制)。
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// 【MySQL LAST_INSERT_ID() 说明】
|
|
|
|
|
|
/// LAST_INSERT_ID() 是 MySQL 的函数,返回当前连接最近生成的自增主键。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 关键点:
|
|
|
|
|
|
/// 1. 是"当前连接"级别的,不受其他连接影响(线程安全)
|
|
|
|
|
|
/// 2. 只对 AUTO_INCREMENT 列有效
|
|
|
|
|
|
/// 3. 一次插入多行时,返回第一行的 ID
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 其他数据库的等效写法:
|
|
|
|
|
|
/// - SQL Server: SELECT SCOPE_IDENTITY()
|
|
|
|
|
|
/// - PostgreSQL: RETURNING id 或 SELECT currval('seq_name')
|
|
|
|
|
|
/// - Oracle: RETURNING id INTO ...(需要存储过程)
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// <para>
|
|
|
|
|
|
/// 【C# 语法知识点 - 为什么这里不用默认参数?】
|
|
|
|
|
|
/// 注意这个方法没有 parameter = null,而是要求必须传 parameter。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 原因:
|
|
|
|
|
|
/// 1. 插入操作必须有数据,不可能无参数插入
|
|
|
|
|
|
/// 2. 强制调用方提供参数,避免空引用错误
|
|
|
|
|
|
/// 3. 语义清晰:插入什么必须明确指定
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 设计原则:
|
|
|
|
|
|
/// - 可选的用默认参数(如查询条件)
|
|
|
|
|
|
/// - 强制的不用默认参数(如插入数据)
|
|
|
|
|
|
/// </para>
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="mapperName">Mapper 名称</param>
|
|
|
|
|
|
/// <param name="statementId">SQL 语句 ID</param>
|
|
|
|
|
|
/// <param name="parameter">插入数据对象(必须)</param>
|
|
|
|
|
|
/// <returns>自增主键值的异步任务</returns>
|
|
|
|
|
|
public async Task<long> InsertReturnIdentityAsync(string mapperName, string statementId, object parameter)
|
|
|
|
|
|
{
|
|
|
|
|
|
HwPortalPreparedSql preparedSql = _registry.Prepare(mapperName, statementId, parameter);
|
|
|
|
|
|
await _db.Ado.ExecuteCommandAsync(preparedSql.Sql, preparedSql.Parameters);
|
|
|
|
|
|
|
|
|
|
|
|
// LAST_INSERT_ID() 是 MySQL 风格的“最近一次自增主键”读取方式。
|
|
|
|
|
|
// 这里为了保持和源 Java/MyBatis 行为一致,先这样处理。
|
|
|
|
|
|
List<long> ids = await _db.Ado.SqlQueryAsync<long>("SELECT LAST_INSERT_ID();");
|
|
|
|
|
|
return ids.FirstOrDefault();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|