功能特性开关
该框架提供了一系列可通过特性开关动态添加或移除的代码生成功能。
使用方式
特性开关可以通过 CLI 标志或作为 gen 包的参数提供。
CLI
go run -mod=mod entgo.io/ent/cmd/ent generate --feature privacy,entql ./ent/schema
Go
// +build ignore
package main
import (
"log"
"text/template"
"entgo.io/ent/entc"
"entgo.io/ent/entc/gen"
)
func main() {
err := entc.Generate("./schema", &gen.Config{
Features: []gen.Feature{
gen.FeaturePrivacy,
gen.FeatureEntQL,
},
Templates: []*gen.Template{
gen.MustParse(gen.NewTemplate("static").
Funcs(template.FuncMap{"title": strings.ToTitle}).
ParseFiles("template/static.tmpl")),
},
})
if err != nil {
log.Fatalf("running ent codegen: %v", err)
}
}
功能特性列表
自动解决合并冲突
schema/snapshot 选项指示 entc (ent 代码生成器) 将最新模式的快照存储在一个内部包中,并在用户模式无法构建时自动解决合并冲突。
此选项可通过 --feature schema/snapshot 标志添加到项目中,但请查看 ent/ent/issues/852 了解相关背景信息。
隐私层
隐私层允许为数据库实体的查询和变更配置隐私策略。
此选项可通过 --feature privacy 标志添加到项目中,您可以在隐私文档中了解更多信息。
EntQL 动态过滤
entql 选项为不同的查询构建器提供了运行时通用动态过滤能力。
此选项可通过 --feature entql 标志添加到项目中,您可以在隐私文档中了解更多信息。
命名边加载
namedges 选项提供了带有自定义名称的边预加载 API。
此选项可通过 --feature namedges 标志添加到项目中,您可以在预加载文档中了解更多信息。
双向边引用
bidiedges 选项引导 Ent 在预加载边 (O2M/O2O) 时设置双向引用。
此选项可通过 --feature bidiedges 标志添加到项目中。
使用标准 encoding/json.MarshalJSON 的用户应在调用 json.Marshal 前解除循环引用。
模式配置
sql/schemaconfig 选项允许您为模型传递替代的 SQL 数据库名称。当您的模型不全部位于同一个数据库下,而是分散在不同模式中时,这非常有用。
此选项可通过 --feature sql/schemaconfig 标志添加到项目中。生成代码后,您可以像这样使用新选项:
c, err := ent.Open(dialect, conn, ent.AlternateSchema(ent.SchemaConfig{
User: "usersdb",
Car: "carsdb",
}))
c.User.Query().All(ctx) // SELECT * FROM `usersdb`.`users`
c.Car.Query().All(ctx) // SELECT * FROM `carsdb`.`cars`
行级锁
sql/lock 选项允许使用 SQL SELECT ... FOR {UPDATE | SHARE} 语法配置行级锁定。
此选项可通过 --feature sql/lock 标志添加到项目中。
tx, err := client.Tx(ctx)
if err != nil {
log.Fatal(err)
}
tx.Pet.Query().
Where(pet.Name(name)).
ForUpdate().
Only(ctx)
tx.Pet.Query().
Where(pet.ID(id)).
ForShare(
sql.WithLockTables(pet.Table),
sql.WithLockAction(sql.NoWait),
).
Only(ctx)
自定义 SQL 修饰器
sql/modifier 选项允许向构建器添加自定义 SQL 修饰器,并在语句执行前修改它们。
此选项可通过 --feature sql/modifier 标志添加到项目中。
修饰示例 1
client.Pet.
Query().
Modify(func(s *sql.Selector) {
s.Select("SUM(LENGTH(name))")
}).
IntX(ctx)
上述代码将生成以下 SQL 查询:
SELECT SUM(LENGTH(name)) FROM `pet`
选择和扫描动态值
如果您使用 SQL 修饰器并需要扫描 Ent 模式定义中不存在的动态值(如聚合或自定义排序),可以对 sql.Selector 应用 AppendSelect/AppendSelectAs。随后可以通过每个实体上定义的 Value 方法访问它们的值:
const as = "name_length"
// 使用动态值查询实体。
p := client.Pet.Query().
Modify(func(s *sql.Selector) {
s.AppendSelectAs("LENGTH(name)", as)
}).
FirstX(ctx)
// 从实体读取值。
n, err := p.Value(as)
if err != nil {
log.Fatal(err)
}
fmt.Println("Name length: %d == %d", n, len(p.Name))
修饰示例 2
var p1 []struct {
ent.Pet
NameLength int `sql:"length"`
}
client.Pet.Query().
Order(ent.Asc(pet.FieldID)).
Modify(func(s *sql.Selector) {
s.AppendSelect("LENGTH(name)")
}).
ScanX(ctx, &p1)
上述代码将生成以下 SQL 查询:
SELECT `pet`.*, LENGTH(name) FROM `pet` ORDER BY `pet`.`id` ASC
修饰示例 3
var v []struct {
Count int `json:"count"`
Price int `json:"price"`
CreatedAt time.Time `json:"created_at"`
}
client.User.
Query().
Where(
user.CreatedAtGT(x),
user.CreatedAtLT(y),
).
Modify(func(s *sql.Selector) {
s.Select(
sql.As(sql.Count("*"), "count"),
sql.As(sql.Sum("price"), "price"),
sql.As("DATE(created_at)", "created_at"),
).
GroupBy("DATE(created_at)").
OrderBy(sql.Desc("DATE(created_at)"))
}).
ScanX(ctx, &v)
上述代码将生成以下 SQL 查询:
SELECT
COUNT(*) AS `count`,
SUM(`price`) AS `price`,
DATE(created_at) AS `created_at`
FROM
`users`
WHERE
`created_at` > x AND `created_at` < y
GROUP BY
DATE(created_at)
ORDER BY
DATE(created_at) DESC
修饰示例 4
var gs []struct {
ent.Group
UsersCount int `sql:"users_count"`
}
client.Group.Query().
Order(ent.Asc(group.FieldID)).
Modify(func(s *sql.Selector) {
t := sql.Table(group.UsersTable)
s.LeftJoin(t).
On(
s.C(group.FieldID),
t.C(group.UsersPrimaryKey[1]),
).
// 将 "users_count" 列追加到选定列中。
AppendSelect(
sql.As(sql.Count(t.C(group.UsersPrimaryKey[1])), "users_count"),
).
GroupBy(s.C(group.FieldID))
}).
ScanX(ctx, &gs)
上述代码将生成以下 SQL 查询:
SELECT
`groups`.*,
COUNT(`t1`.`group_id`) AS `users_count`
FROM
`groups` LEFT JOIN `user_groups` AS `t1`
ON
`groups`.`id` = `t1`.`group_id`
GROUP BY
`groups`.`id`
ORDER BY
`groups`.`id` ASC
修饰示例 5
client.User.Update().
Modify(func(s *sql.UpdateBuilder) {
s.Set(user.FieldName, sql.Expr(fmt.Sprintf("UPPER(%s)", user.FieldName)))
}).
ExecX(ctx)
上述代码将生成以下 SQL 查询:
UPDATE `users` SET `name` = UPPER(`name`)
修饰示例 6
client.User.Update().
Modify(func(u *sql.UpdateBuilder) {
u.Set(user.FieldID, sql.ExprFunc(func(b *sql.Builder) {
b.Ident(user.FieldID).WriteOp(sql.OpAdd).Arg(1)
}))
u.OrderBy(sql.Desc(user.FieldID))
}).
ExecX(ctx)
上述代码将生成以下 SQL 查询:
UPDATE `users` SET `id` = `id` + 1 ORDER BY `id` DESC
修饰示例 7
向 JSON 列的 values 数组追加元素:
client.User.Update().
Modify(func(u *sql.UpdateBuilder) {
sqljson.Append(u, user.FieldTags, []string{"tag1", "tag2"}, sqljson.Path("values"))
}).
ExecX(ctx)
上述代码将生成以下 SQL 查询:
UPDATE `users` SET `tags` = CASE
WHEN (JSON_TYPE(JSON_EXTRACT(`tags`, '$.values')) IS NULL OR JSON_TYPE(JSON_EXTRACT(`tags`, '$.values')) = 'NULL')
THEN JSON_SET(`tags`, '$.values', JSON_ARRAY(?, ?))
ELSE JSON_ARRAY_APPEND(`tags', '$.values', ?, '$.values', ?) END
WHERE `id` = ?
SQL 原生 API
sql/execquery 选项允许使用底层驱动程序的 ExecContext/QueryContext 方法执行语句。完整文档请参见:DB.ExecContext 和 DB.QueryContext。
// 从 ent.Client 执行。
if _, err := client.ExecContext(ctx, "TRUNCATE t1"); err != nil {
return err
}
// 从 ent.Tx 执行。
tx, err := client.Tx(ctx)
if err != nil {
return err
}
if err := tx.User.Create().Exec(ctx); err != nil {
return err
}
if _, err := tx.ExecContext("SAVEPOINT user_created"); err != nil {
return err
}
// ...
使用 ExecContext/QueryContext 执行的语句不会经过 Ent,可能会跳过应用程序中的基础层,例如钩子、隐私(授权)和验证器。
插入或更新 (Upsert)
sql/upsert 选项允许使用 SQL ON CONFLICT / ON DUPLICATE KEY 语法配置插入或更新及批量插入或更新逻辑。完整文档请参见:Upsert API。
此选项可通过 --feature sql/upsert 标志添加到项目中。
// 使用创建时设置的新值。
id, err := client.User.
Create().
SetAge(30).
SetName("Ariel").
OnConflict().
UpdateNewValues().
ID(ctx)
// 在 PostgreSQL 中,冲突目标是必需的。
err := client.User.
Create().
SetAge(30).
SetName("Ariel").
OnConflictColumns(user.FieldName).
UpdateNewValues().
Exec(ctx)
// 也支持批量插入或更新。
client.User.
CreateBulk(builders...).
OnConflict(
sql.ConflictWhere(...),
sql.UpdateWhere(...),
).
UpdateNewValues().
Exec(ctx)
// INSERT INTO "users" (...) VALUES ... ON CONFLICT WHERE ... DO UPDATE SET ... WHERE ...
全局唯一 ID
默认情况下,SQL 主键从每个表的 1 开始;这意味着不同类型的多个实体可以共享相同的 ID。这与 AWS Neptune 不同,后者的节点 ID 是 UUID。
如果您使用 GraphQL,这不太适用,因为 GraphQL 要求对象 ID 唯一。
要为您的项目启用全局 ID 支持,只需使用 --feature sql/globalid 标志。
如果您过去使用过 migrate.WithGlobalUniqueID(true) 迁移选项,请在将项目切换到新的 globalid 特性前阅读本指南。
它是如何工作的? ent 迁移为每个实体(表)的 ID 分配一个 1<<32 的范围,并将此信息与生成的代码 (internal/globalid.go) 一起存储。例如,类型 A 的 ID 范围为 [1,4294967296),类型 B 的 ID 范围为 [4294967296,8589934592),依此类推。
请注意,如果启用此选项,可能的最大表数为 65535。