Skip to content

定义实体

这一页解决三个问题:类要加什么宏、字段怎么命名、数据库映射规则怎么定。

适用场景

  • 准备定义数据库表实体
  • 想区分 @Entity@Model@Serialization
  • 需要明确主键、列名、并发字段这些映射规则

核心问题

SqlSharp 里的“实体”不是普通数据类。它需要同时服务两件事:

  1. 让查询结果能映射回对象。
  2. 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")字段
主键@Keyentity.hasKey(["prop"])字段
复合主键entity.hasKey(["prop1", "prop2"])
排除字段@NotMappedentity.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 = None
cangjie
mb.entity<Order>().ignore("tempCache")

并发检查

更新时将该字段加入 WHERE 条件,防止并发覆盖:

cangjie
@ConcurrencyCheck
private var _version: Int64 = 0
cangjie
mb.entity<Order>()
  .property("version")
  .isConcurrencyToken()

值生成策略

数据库自动生成值的字段(如自增主键)默认行为是 Identity。如果手动赋值,需要关闭自动生成:

cangjie
@DatabaseGenerated[DatabaseGeneratedOption.Never]
private var _manualId: Int64 = 0
cangjie
mb.entity<Order>()
  .property("manualId")
  .valueGeneratedNever()

常见字段类型

仓颉类型推荐数据库列类型可空写法
BoolBOOLEAN?Bool
Int8 ~ Int64TINYINT ~ BIGINT?Int8 ~ ?Int64
Float32 / Float64FLOAT / DOUBLE?Float32 / ?Float64
DecimalDECIMAL(p,s)?Decimal
StringVARCHAR(n) / TEXT?String
DateTimeDATETIME(6)?DateTime
JsonValueJSON?JsonValue

与其他模块组合

  • 实体定义好后,下一步就是在 DbContext 里暴露 DbSet<T>
  • @Serialization 宏负责对象与查询结果之间的字段映射,包说明见 SqlSharp 总览 - 依赖说明
  • 并发字段会直接影响后续 变更跟踪 的更新行为
  • 查询参数对象通常和 Queryable 查询 一起出现

生产建议

  • 实体类优先只保留持久化相关字段,不要把过多临时状态混进去
  • DTO、参数对象、实体对象分开定义,避免一个类承担三种职责
  • 业务上需要防并发覆盖时,尽早设计 @ConcurrencyCheck 字段