手写 SQL
这一页关注 SqlSharp 的另一条路径:保留对象映射和参数绑定,但直接手写 SQL。
适用场景
Queryable不足以表达复杂查询- 需要执行
UPDATE、DELETE、DDL 或数据库方言 SQL - 希望复用 SqlSharp 的参数绑定、结果映射和类型映射能力
核心问题
SqlSharp 没有强迫你只能走链式查询。DbContext 直接提供了三类入口:
execute()执行增删改和 DDLquery<T>()查询列表executeScalar<T>()查询标量值
快速开始
手写 SQL 的最短路径——参数绑定 + 查询列表:
cangjie
let p = DbParameters()
p.add("minPrice", Int64(100))
let list = ctx.query<Product>(
"SELECT id, name, price FROM products WHERE price > @minPrice",
p
)@minPrice 会被解析为 ?,参数值通过 TypeMapper 安全绑定,结果行自动映射回 Product 对象。
除了 query<T>(),还有 execute()(增删改/DDL)和 executeScalar<T>()(标量值)两个入口,详见下方三类入口。
核心概念
参数类型
execute()、query<T>()、executeScalar<T>() 的第二个参数是 Any,支持以下四种类型:
| 类型 | 示例 | 适用场景 |
|---|---|---|
DbParameters | DbParameters().add("id", 1) | 手写参数绑定 |
Map<String, Any> | {"id": Int64(1), "name": "A"} | 少量参数快速传值 |
Collection<(String, Any)> | [("id", Int64(1)), ("name", "A")] | 保持参数顺序 |
@Model / @Serialization 对象 | ctx.query<T>(sql, model) | 实体对象直接作为参数源 |
cangjie
// 方式 1:DbParameters(推荐,类型安全)
let p = DbParameters()
p.add("minPrice", Int64(100))
ctx.query<Product>("SELECT * FROM products WHERE price > @minPrice", p)
// 方式 2:Map
ctx.query<Product>("SELECT * FROM products WHERE id = @id", {"id": Int64(1)})
// 方式 3:Collection 元组
ctx.query<Product>("SELECT * FROM products WHERE id = @id AND name = @name",
[("id", Int64(1)), ("name", "Widget")])
// 方式 4:实体对象(自动提取属性值)
let filter = ProductFilter(minPrice: 100, maxPrice: 999)
ctx.query<Product>("SELECT * FROM products WHERE price > @minPrice AND price < @maxPrice", filter)推荐优先使用 DbParameters——它提供明确的方法签名,编译器可以检查参数名拼写,值类型也有保证。
结果映射规则
当 SQL 返回列名和属性名不完全一致时,SqlSharp 会自动做两层匹配:
| 优先级 | 规则 | 示例 |
|---|---|---|
| 1 | 忽略大小写精确匹配 | id → id |
| 2 | 去掉下划线后再匹配 | user_name → userName |
如果都匹配不上,会跳过该列,不会因为单列失败而中断整个结果映射。
三类入口
DbContext 提供三个方法直接执行 SQL:
cangjie
// query<T>() — 查询列表,结果自动映射为对象
let list = ctx.query<Product>("SELECT * FROM products WHERE price > @min", p)
// execute() — 执行增删改或 DDL,返回影响行数
ctx.execute("UPDATE products SET price = @price WHERE id = @id", p)
// executeScalar<T>() — 查询标量值(COUNT、MAX 等)
let cnt = ctx.executeScalar<Int64>("SELECT COUNT(*) FROM products")不需要 @Entity 的 DTO 也可以参与映射,只要具备序列化能力即可。
常见用法
IN 查询
集合参数可以自动展开:
cangjie
let p = DbParameters()
p.add("ids", [1, 2, 3])
let list = ctx.query<Product>(
"SELECT * FROM products WHERE id IN @ids",
p
)执行 DDL
execute() 同样可以执行建表、加索引等 DDL 语句:
cangjie
ctx.execute("CREATE TABLE t_log (id BIGINT PRIMARY KEY, msg TEXT)")
ctx.execute("ALTER TABLE products ADD COLUMN stock INT DEFAULT 0")动态拼接
需要按条件动态拼接 SQL 时,可以先构造片段:
cangjie
let sb = SqlBuilder()
sb.whereIf("price > @minPrice", minPrice > 0)
sb.whereIf("name IS NOT NULL", needName)
sb.orderBy("price DESC")
let sql = "SELECT * FROM products${sb.whereSql}${sb.orderSql}"
ctx.query<Product>(sql, params)与其他模块组合
生产建议
- 手写 SQL 不是退路,而是复杂场景下的正常选择
- SQL 文本里只放结构,参数一律用
DbParameters - 动态排序、动态字段名不要直接拼外部输入
常见问题
手写 SQL 会不会丢掉对象映射能力
不会。只要目标类型具备序列化映射能力,query<T>() 仍然会把列映射回对象。
DTO 需要 @Entity 吗
不需要。只做结果映射时,通常只要 @Serialization。