定义实体
这一页解决三个问题:类要加什么宏、字段怎么命名、数据库映射规则怎么定。
适用场景
- 准备定义数据库表实体
- 想区分
@Entity、@Model、@Serialization - 需要明确主键、列名、并发字段这些映射规则
核心问题
SqlSharp 里的“实体”不是普通数据类。它需要同时服务两件事:
- 让查询结果能映射回对象。
- 让
ChangeTracker能跟踪对象变化。
因此最常见的实体定义会同时包含:
@Entity@Serialization
快速开始
一个完整的实体定义最少需要这些:
cangjie
import sqlsharp.*
import sqlsharp.macros.*
import sqlsharp.annotations.*
import sqlsharp.serialization.*
@Entity
@Serialization
public class Product {
private var _id: Int64 = 0
private var _name: String = ""
private var _price: Int64 = 0
}定义好实体后,在 DbContext 中暴露 DbSet<Product> 即可参与 CRUD,详见 DbContext 配置。
核心概念
三个常用宏
| 场景 | 宏组合 | 作用 |
|---|---|---|
| 数据库表实体 | @Entity + @Serialization | 支持变更跟踪和结果映射 |
| 查询参数对象 | @Model + @Serialization | 支持 params(model) 自动取值 |
| 只读 DTO / 投影结果 | @Serialization | 只做结果映射,不参与跟踪 |
字段约定
- 字段名以下划线开头,例如
_createdAt - 字段必须是
var - 宏会生成去掉下划线的公开属性,例如
_createdAt对应createdAt - 默认列名等于属性名,可再通过注解或 FluentAPI 覆盖
cangjie
// 字段 _userName → 属性 userName → 列名 user_name(@Column 覆盖)
// 字段 _createdAt → 属性 createdAt → 列名 created_at(@Column 覆盖)
public class User {
private var _id: Int64 = 0
@Column["user_name"]
private var _userName: String = ""
@Column["created_at"]
private var _createdAt: DateTime = DateTime.now()
}常见用法
每种映射配置都支持两种方式:注解写在实体类上,FluentAPI 写在 DbContext.onModelCreating 中。FluentAPI 优先级高于注解。
速查表
| 配置项 | 注解 | FluentAPI | 位置 |
|---|---|---|---|
| 表名 | @Table["t_order"] | entity.toTable("t_order") | 类 |
| Schema | @Table["t_order", "public"] | entity.toTable("t_order", "public") | 类 |
| 列名 | @Column["col_name"] | .property("prop").hasColumnName("col_name") | 字段 |
| 主键 | @Key | entity.hasKey(["prop"]) | 字段 |
| 复合主键 | — | entity.hasKey(["prop1", "prop2"]) | 类 |
| 排除字段 | @NotMapped | entity.ignore("prop") | 字段 |
| 并发检查 | @ConcurrencyCheck | .property("prop").isConcurrencyToken() | 字段 |
| 值不自动生成 | @DatabaseGenerated[DatabaseGeneratedOption.Never] | .property("prop").valueGeneratedNever() | 字段 |
表名
cangjie
@Entity
@Serialization
@Table["t_order"]
public class Order {
private var _id: Int64 = 0
}cangjie
protected override func onModelCreating(mb: ModelBuilder): Unit {
mb.entity<Order>().toTable("t_order")
}cangjie
public class AppDbContext <: DbContext {
public prop orders: DbSet<Order> {
get() { set<Order>() }
}
public override func onConfiguring(b: DbContextOptionsBuilder): Unit {
b.useMysql("mariadb://localhost:3306?username=root&password=1024&database=mydb", "mariadb")
}
}表名的确定优先级从高到低:@Table 注解 > FluentAPI toTable() > DbSet<T> 属性名。以上例来说,如果三者都没指定,框架会取 DbSet<T> 的属性名 orders 作为表名。
带 Schema 的场景:
cangjie
@Table["t_order", "public"]
public class Order {
private var _id: Int64 = 0
}cangjie
mb.entity<Order>().toTable("t_order", "public")列名
cangjie
@Column["user_name"]
private var _userName: String = ""cangjie
mb.entity<User>()
.property("userName")
.hasColumnName("user_name")主键
单个主键两种写法等价:
cangjie
public class Product {
@Key
@Column["sku"]
private var _sku: String = ""
}cangjie
mb.entity<Product>().hasKey(["sku"])复合主键只能用 FluentAPI:
cangjie
mb.entity<OrderItem>().toTable("order_item").hasKey(["orderId", "lineNo"])识别规则: 没有 @Key 也没有 FluentAPI 时,按以下顺序判定:
| 条件 | 结果 |
|---|---|
字段名 _id 且为整数类型 | 自增主键 |
字段名 _id 但不是整数(如 String) | 不自增 |
打 @Key | 视为主键 |
FluentAPI hasKey([...]) | 以 FluentAPI 为准 |
排除字段
让某个字段不参与数据库映射:
cangjie
@NotMapped
private var _tempCache: ?String = Nonecangjie
mb.entity<Order>().ignore("tempCache")并发检查
更新时将该字段加入 WHERE 条件,防止并发覆盖:
cangjie
@ConcurrencyCheck
private var _version: Int64 = 0cangjie
mb.entity<Order>()
.property("version")
.isConcurrencyToken()值生成策略
数据库自动生成值的字段(如自增主键)默认行为是 Identity。如果手动赋值,需要关闭自动生成:
cangjie
@DatabaseGenerated[DatabaseGeneratedOption.Never]
private var _manualId: Int64 = 0cangjie
mb.entity<Order>()
.property("manualId")
.valueGeneratedNever()常见字段类型
| 仓颉类型 | 推荐数据库列类型 | 可空写法 |
|---|---|---|
Bool | BOOLEAN | ?Bool |
Int8 ~ Int64 | TINYINT ~ BIGINT | ?Int8 ~ ?Int64 |
Float32 / Float64 | FLOAT / DOUBLE | ?Float32 / ?Float64 |
Decimal | DECIMAL(p,s) | ?Decimal |
String | VARCHAR(n) / TEXT | ?String |
DateTime | DATETIME(6) | ?DateTime |
JsonValue | JSON | ?JsonValue |
与其他模块组合
- 实体定义好后,下一步就是在
DbContext里暴露DbSet<T> @Serialization宏负责对象与查询结果之间的字段映射,包说明见 SqlSharp 总览 - 依赖说明- 并发字段会直接影响后续 变更跟踪 的更新行为
- 查询参数对象通常和 Queryable 查询 一起出现
生产建议
- 实体类优先只保留持久化相关字段,不要把过多临时状态混进去
- DTO、参数对象、实体对象分开定义,避免一个类承担三种职责
- 业务上需要防并发覆盖时,尽早设计
@ConcurrencyCheck字段