跳到主要内容

CRUD API

介绍章节所述,在模式(schemas)上运行 ent 将会生成以下资产:

  • 用于与图(graph)交互的 ClientTx 对象。
  • 每种模式类型的 CRUD 构建器。
  • 每种模式类型的实体对象(Go 结构体)。
  • 包含用于与构建器交互的常量和谓词的包。
  • 用于 SQL 方言的 migrate 包。更多信息请参阅迁移

创建新客户端

package main

import (
"context"
"log"

"entdemo/ent"

_ "github.com/mattn/go-sqlite3"
)

func main() {
client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
if err != nil {
log.Fatalf("failed opening connection to sqlite: %v", err)
}
defer client.Close()
// 运行自动迁移工具。
if err := client.Schema.Create(context.Background()); err != nil {
log.Fatalf("failed creating schema resources: %v", err)
}
}

创建实体

保存 (Save) 一个用户。

a8m, err := client.User.    // UserClient。
Create(). // 用户创建构建器。
SetName("a8m"). // 设置字段值。
SetNillableAge(age). // 避免空值检查。
AddGroups(g1, g2). // 添加多条边。
SetSpouse(nati). // 设置唯一边。
Save(ctx) // 创建并返回。

保存X (SaveX) 一个宠物;与 Save 不同,SaveX 在发生错误时会触发 panic。

pedro := client.Pet.    // PetClient。
Create(). // 宠物创建构建器。
SetName("pedro"). // 设置字段值。
SetOwner(a8m). // 设置主人(唯一边)。
SaveX(ctx) // 创建并返回。

批量创建

保存 (Save) 一批宠物。

pets, err := client.Pet.CreateBulk(
client.Pet.Create().SetName("pedro").SetOwner(a8m),
client.Pet.Create().SetName("xabi").SetOwner(a8m),
client.Pet.Create().SetName("layla").SetOwner(a8m),
).Save(ctx)

names := []string{"pedro", "xabi", "layla"}
pets, err := client.Pet.MapCreateBulk(names, func(c *ent.PetCreate, i int) {
c.SetName(names[i]).SetOwner(a8m)
}).Save(ctx)

更新单个实体

更新一个从数据库返回的实体。

a8m, err = a8m.Update().    // 用户更新构建器。
RemoveGroup(g2). // 移除特定边。
ClearCard(). // 清除唯一边。
SetAge(30). // 设置字段值。
AddRank(10). // 递增字段值。
AppendInts([]int{1}). // 向 JSON 数组追加值。
Save(ctx) // 保存并返回。

通过 ID 更新

pedro, err := client.Pet.   // PetClient。
UpdateOneID(id). // 宠物更新构建器。
SetName("pedro"). // 设置字段名称。
SetOwnerID(owner). // 使用 ID 设置唯一边。
Save(ctx) // 保存并返回。

带条件的更新单个实体

在某些项目中,“更新多个”操作不被允许,并通过钩子(hooks)进行阻止。然而,仍然需要通过 ID 更新单个实体,同时确保其满足特定条件。在这种情况下,你可以使用 Where 选项,如下所示:

err := client.Todo.
UpdateOneID(id).
SetStatus(todo.StatusDone).
AddVersion(1).
Where(
todo.Version(currentVersion),
).
Exec(ctx)
switch {
// 如果实体不满足特定条件,
// 操作将返回一个 "ent.NotFoundError"。
case ent.IsNotFound(err):
fmt.Println("todo item was not found")
// 任何其他错误。
case err != nil:
fmt.Println("update error:", err)
}

更新多个实体

使用谓词进行筛选。

n, err := client.User.          // UserClient。
Update(). // 用户更新构建器。
Where( //
user.Or( // (年龄 >= 30 OR 名称 = "bar")
user.AgeGT(30), //
user.Name("bar"), // AND
), //
user.HasFollowers(), // UserHasFollowers()
). //
SetName("foo"). // 设置字段名称。
Save(ctx) // 执行并返回。

查询边谓词。

n, err := client.User.          // UserClient。
Update(). // 用户更新构建器。
Where( //
user.HasFriendsWith( // UserHasFriendsWith (
user.Or( // 年龄 = 20
user.Age(20), // 或
user.Age(30), // 年龄 = 30
) // )
), //
). //
SetName("a8m"). // 设置字段名称。
Save(ctx) // 执行并返回。

更新或插入单个实体 (Upsert One)

Ent 支持使用 sql/upsert 特性标志来更新或插入记录。

err := client.User.
Create().
SetAge(30).
SetName("Ariel").
OnConflict().
// 使用创建时设置的新值。
UpdateNewValues().
Exec(ctx)

id, err := client.User.
Create().
SetAge(30).
SetName("Ariel").
OnConflict().
// 使用创建时设置的 "age"。
UpdateAge().
// 在冲突时设置不同的 "name"。
SetName("Mashraki").
ID(ctx)

// 自定义 UPDATE 子句。
err := client.User.
Create().
SetAge(30).
SetName("Ariel").
OnConflict().
UpdateNewValues().
// 使用自定义更新覆盖某些字段。
Update(func(u *ent.UserUpsert) {
u.SetAddress("localhost")
u.AddCount(1)
u.ClearPhone()
}).
Exec(ctx)

在 PostgreSQL 中,需要指定冲突目标

// 使用 Fluent API 设置列名。
err := client.User.
Create().
SetName("Ariel").
OnConflictColumns(user.FieldName).
UpdateNewValues().
Exec(ctx)

// 使用 SQL API 设置列名。
err := client.User.
Create().
SetName("Ariel").
OnConflict(
sql.ConflictColumns(user.FieldName),
).
UpdateNewValues().
Exec(ctx)

// 使用 SQL API 设置约束名称。
err := client.User.
Create().
SetName("Ariel").
OnConflict(
sql.ConflictConstraint(constraint),
).
UpdateNewValues().
Exec(ctx)

为了自定义执行的语句,请使用 SQL API:

id, err := client.User.
Create().
OnConflict(
sql.ConflictColumns(...),
sql.ConflictWhere(...),
sql.UpdateWhere(...),
).
Update(func(u *ent.UserUpsert) {
u.SetAge(30)
u.UpdateName()
}).
ID(ctx)

// INSERT INTO "users" (...) VALUES (...) ON CONFLICT WHERE ... DO UPDATE SET ... WHERE ...
信息

由于 upsert API 是使用 ON CONFLICT 子句(在 MySQL 中是 ON DUPLICATE KEY)实现的,因此 Ent 仅向数据库执行一条语句,因此,此类操作仅应用创建钩子

更新或插入多个实体 (Upsert Many)

err := client.User.             // UserClient
CreateBulk(builders...). // 用户批量创建。
OnConflict(). // 用户批量更新或插入。
UpdateNewValues(). // 在冲突时使用创建时设置的值。
Exec(ctx) // 执行语句。

查询图 (Graph)

获取所有有关注者的用户。

users, err := client.User.      // UserClient。
Query(). // 用户查询构建器。
Where(user.HasFollowers()). // 仅筛选有关注者的用户。
All(ctx) // 查询并返回。

获取特定用户的所有关注者;从图中的节点开始遍历。

users, err := a8m.
QueryFollowers().
All(ctx)

获取用户的所有关注者的宠物。

users, err := a8m.
QueryFollowers().
QueryPets().
All(ctx)

统计没有评论的帖子数量。

n, err := client.Post.
Query().
Where(
post.Not(
post.HasComments(),
)
).
Count(ctx)

更高级的遍历可以在下一节中找到。

字段选择

获取所有宠物名称。

names, err := client.Pet.
Query().
Select(pet.FieldName).
Strings(ctx)

获取所有不重复的宠物名称。

names, err := client.Pet.
Query().
Unique(true).
Select(pet.FieldName).
Strings(ctx)

统计不重复宠物名称的数量。

n, err := client.Pet.
Query().
Unique(true).
Select(pet.FieldName).
Count(ctx)

选择部分对象和部分关联。 获取所有宠物及其主人,但仅选择并填充 IDName 字段。

pets, err := client.Pet.
Query().
Select(pet.FieldName).
WithOwner(func (q *ent.UserQuery) {
q.Select(user.FieldName)
}).
All(ctx)

将所有宠物名称和年龄扫描到自定义结构体。

var v []struct {
Age int `json:"age"`
Name string `json:"name"`
}
err := client.Pet.
Query().
Select(pet.FieldAge, pet.FieldName).
Scan(ctx, &v)
if err != nil {
log.Fatal(err)
}

更新一个实体并返回其部分字段。

pedro, err := client.Pet.
UpdateOneID(id).
SetAge(9).
SetName("pedro").
// Select 允许选择返回实体的一个或多个字段(列)。
// 默认是选择实体模式中定义的所有字段。
Select(pet.FieldName).
Save(ctx)

删除单个实体

删除一个实体:

err := client.User.
DeleteOne(a8m).
Exec(ctx)

通过 ID 删除:

err := client.User.
DeleteOneID(id).
Exec(ctx)

带条件的删除单个实体

在某些项目中,“删除多个”操作不被允许,并通过钩子(hooks)进行阻止。然而,仍然需要通过 ID 删除单个实体,同时确保其满足特定条件。在这种情况下,你可以使用 Where 选项,如下所示:

err := client.Todo.
DeleteOneID(id).
Where(
// 仅允许删除过期的待办事项。
todo.ExpireLT(time.Now()),
).
Exec(ctx)
switch {
// 如果实体不满足特定条件,
// 操作将返回一个 "ent.NotFoundError"。
case ent.IsNotFound(err):
fmt.Println("todo item was not found")
// 任何其他错误。
case err != nil:
fmt.Println("deletion error:", err)
}

删除多个实体

使用谓词删除:

affected, err := client.File.
Delete().
Where(file.UpdatedAtLT(date)).
Exec(ctx)

变更 (Mutation)

每个生成的节点类型都有其自己的变更(Mutation)类型。例如,所有 User 构建器共享同一个生成的 UserMutation 对象。 然而,所有构建器类型都实现了通用的 ent.Mutation 接口。

例如,为了编写在 ent.UserCreateent.UserUpdate 上都应用一组方法的通用代码,请使用 UserMutation 对象:

func Do() {
creator := client.User.Create()
SetAgeName(creator.Mutation())
updater := client.User.UpdateOneID(id)
SetAgeName(updater.Mutation())
}

// SetAgeName 为任何变更设置年龄和名称。
func SetAgeName(m *ent.UserMutation) {
m.SetAge(32)
m.SetName("Ariel")
}

在某些情况下,你希望在多种类型上应用一组方法。 对于这种情况,要么使用通用的 ent.Mutation 接口, 要么创建你自己的接口。

func Do() {
creator1 := client.User.Create()
SetName(creator1.Mutation(), "a8m")

creator2 := client.Pet.Create()
SetName(creator2.Mutation(), "pedro")
}

// SetNamer 包装了用于在变更中获取
// 和设置 "name" 字段的两个方法。
type SetNamer interface {
SetName(string)
Name() (string, bool)
}

func SetName(m SetNamer, name string) {
if _, exist := m.Name(); !exist {
m.SetName(name)
}
}