数据访问
你已经理解了分层架构、仓储层和分页扩展。这一篇专门讲 SQLSharp 在项目里的几个关键用法和工程习惯——这些是后面内容模块和认证模块的数据访问基础。
它在项目里承担了什么
SQLSharp 在这个项目里做四件事:
- DbContext — 统一数据库入口,每个请求共用一个实例
- 实体映射 — 表和视图映射到仓颉类
- Queryable 查询 —
whereIf+toPageResult链式查询 - DB-First 代码生成 — 从数据库生成实体,而不是手写
DbContext 的注册和生命周期
AdminDbContext 在 InfrastructureModule 里注册:
// InfrastructureModule.cj
context.services.addDbContext<AdminDbContext> {
options => options.useMysql(connectionString, "mariadb")
}addDbContext 默认是 Scoped 生命周期——一个 HTTP 请求内共用一个实例。这意味着同一个请求里的 Repository、Queries、Service 拿到的都是同一个 DbContext,事务才能统一控制。
AdminDbContext 本身的结构:每个表一个 DbSet 属性,每个视图一个 DbSet 属性:
public class AdminDbContext <: DbContext & IServiceFactory<AdminDbContext> {
// 表
public prop users: DbSet<User> { get() { this.set<User>() } }
public prop blogPosts: DbSet<BlogPost> { get() { this.set<BlogPost>() } }
public prop comments: DbSet<Comment> { get() { this.set<Comment>() } }
public prop categories: DbSet<Category> { get() { this.set<Category>() } }
public prop tags: DbSet<Tag> { get() { this.set<Tag>() } }
public prop postTags: DbSet<PostTag> { get() { this.set<PostTag>() } }
public prop stars: DbSet<Star> { get() { this.set<Star>() } }
public prop follows: DbSet<Follow> { get() { this.set<Follow>() } }
// 视图
public prop vBlogPosts: DbSet<VBlogPost> { get() { this.set<VBlogPost>() } }
public prop vComments: DbSet<VComment> { get() { this.set<VComment>() } }
public prop vUserProfiles: DbSet<VUserProfile> { get() { this.set<VUserProfile>() } }
// ... 其他视图
}为什么大量使用视图
这个项目里,读操作几乎全部查视图,不查原始表。
看看 v_blog_post 视图的定义(简化版):
CREATE VIEW v_blog_post AS
SELECT
p.*,
c.code AS category_code,
c.label AS category_label,
u.username AS author_username,
u.avatar AS author_avatar,
COALESCE(s.star_count, 0) AS stars,
COALESCE(cm.comment_count, 0) AS comments
FROM blog_post p
JOIN category c ON c.id = p.category_id
JOIN user u ON u.id = p.author_id
LEFT JOIN (SELECT post_id, COUNT(*) AS star_count FROM star GROUP BY post_id) s ...
LEFT JOIN (SELECT post_id, COUNT(*) AS comment_count FROM comment GROUP BY post_id) cm ...一个视图就把文章 + 分类 + 作者 + 统计全部 join 好了。对应的仓颉视图实体:
// VBlogPost.cj — 代码生成
public class VBlogPost <: Entity {
// 文章字段
public mut prop title: String
public mut prop slug: String
public mut prop content: String
// 分类字段(来自 join)
public mut prop categoryCode: String
public mut prop categoryLabel: String
public mut prop categoryColor: String
// 作者字段(来自 join)
public mut prop authorId: Int64
public mut prop authorUsername: String
public mut prop authorAvatar: String
// 统计字段(来自子查询)
public mut prop stars: Int64
public mut prop comments: Int64
}好处是应用层代码完全不用写 join 和 count。对于内容展示型项目(博客、CMS),这是效率最高的方式。如果视图查询变慢(数据量大以后),加索引或升级为物化视图即可。
whereIf 的两种用法
项目里 whereIf 有两种写法,用在不同场景:
固定条件(始终生效):
.whereIf("is_public = 1") // 只要一个参数,始终添加条件参数(有值才生效):
.whereIf("category_code = @categoryCode", model.categoryCode.isSome())
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^
// SQL 条件 这个条件是否生效第二个参数是 Bool。model.categoryCode.isSome() 返回 true 时条件才拼进 SQL。这意味着调用方不传分类,后端就不按分类筛选。
DbParameters 什么时候用
简单场景直接用字符串插值:
.whereIf("id = ${id}") // id 是整数,无注入风险复杂场景用参数化:
let param = DbParameters()
param.add("slug", slug)
context.vBlogPosts.params(param).whereIf("slug = @slug") // slug 是字符串,防注入规则:整数可以直接 ${} 拼接,字符串必须通过 @参数 绑定的方式防 SQL 注入。
DB-First 代码生成
实体不是手写的,是 sqlsharp_utils 工具从数据库反向生成的。生成入口是 entity.bat:
cd web-api
./entity.bat生成的文件分布在三个目录:
| 目录 | 来源 | 用途 |
|---|---|---|
domain/entities/ | 数据库表 | CRUD 操作 |
domain/views/ | 数据库视图 | 读操作(列表、详情) |
domain/models/ | 表结构 | 传给客户端的 DTO |
表结构变了就重跑——不要手改生成的文件。这是数据库驱动项目的标准工程习惯。
常见问题
ConnectionStrings 里为什么用 mariadb 协议
连接字符串的 scheme 是 mariadb://,不是 mysql://。options.useMysql(connectionString, "mariadb") 里的第二个参数 "mariadb" 指定了驱动类型。这是 SQLSharp 的连接方式。
视图和表在代码里怎么区分
表实体(BlogPost)用于增删改操作,视图实体(VBlogPost)用于查询。区分方式就是类名前缀 V——这是代码生成工具的约定。
什么时候用 params,什么时候不用
需要传变量参数时用 params(param) + @参数名 占位。固定条件直接写在字符串里不需要 params:
// 需要 params
let param = DbParameters()
param.add("authorId", authorId)
context.blogPosts.params(param).whereIf("author_id = @authorId")
// 不需要 params
context.blogPosts.whereIf("is_public = 1")延伸阅读
- SQLSharp 基础 → SqlSharp 总览
- 实体定义 → 定义实体
- DbContext 配置 → DbContext 配置
- Queryable 查询 → Queryable 查询