跳到主要内容

增删改查接口

就像在 代码生成介绍 中提到的,在模式上运行 ent会生成如下资产:

  • 用来与图互操作的 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()
// Run the auto migration tool.
if err := client.Schema.Create(context.Background()); err != nil {
log.Fatalf("failed creating schema resources: %v", err)
}
}

创建实体

保存 Save 用户。

a8m, err := client.User.	// UserClient.
Create(). // User create builder.
SetName("a8m"). // Set field value.
SetNillableAge(age). // Avoid nil checks.
AddGroups(g1, g2). // Add many edges.
SetSpouse(nati). // Set unique edge.
Save(ctx) // Create and return.

SaveX 一个宠物; 与 Save 不同,如果出现错误 SaveX 会使程序崩溃。

pedro := client.Pet.	// PetClient.
Create(). // Pet create builder.
SetName("pedro"). // Set field value.
SetOwner(a8m). // Set owner (unique edge).
SaveX(ctx) // Create and return.

创建多个实体

保存 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().	// User update builder.
RemoveGroup(g2). // Remove a specific edge.
ClearCard(). // Clear a unique edge.
SetAge(30). // Set a field value.
AddRank(10). // Increment a field value.
AppendInts([]int{1}). // Append values to a JSON array.
Save(ctx) // Save and return.

通过ID更新实体

pedro, err := client.Pet.	// PetClient.
UpdateOneID(id). // Pet update builder.
SetName("pedro"). // Set field name.
SetOwnerID(owner). // Set unique edge, using id.
Save(ctx) // Save and return.

通过条件更新实体

在某些项目中,禁止执行“批量更新”操作,并通过钩子进行拦截。 但仍需根据ID更新单个实体,同时确保其满足特定条件。此时可使用 Where 选项,具体如下:

err := client.Todo.
UpdateOneID(id).
SetStatus(todo.StatusDone).
AddVersion(1).
Where(
todo.Version(currentVersion),
).
Exec(ctx)
switch {
// If the entity does not meet a specific condition,
// the operation will return an "ent.NotFoundError".
case ent.IsNotFound(err):
fmt.Println("todo item was not found")
// Any other error.
case err != nil:
fmt.Println("update error:", err)
}

更新多个实体

使用谓词进行过滤。

n, err := client.User.			// UserClient.
Update(). // User update builder.
Where( //
user.Or( // (age >= 30 OR name = "bar")
user.AgeGT(30), //
user.Name("bar"), // AND
), //
user.HasFollowers(), // UserHasFollowers()
). //
SetName("foo"). // Set field name.
Save(ctx) // exec and return.

查询边谓词。

n, err := client.User.			// UserClient.
Update(). // User update builder.
Where( //
user.HasFriendsWith( // UserHasFriendsWith (
user.Or( // age = 20
user.Age(20), // OR
user.Age(30), // age = 30
) // )
), //
). //
SetName("a8m"). // Set field name.
Save(ctx) // exec and return.

更新或插入一个实体

Ent 支持使用 sql/upsert 功能开关 更新或插入 数据记录。

err := client.User.
Create().
SetAge(30).
SetName("Ariel").
OnConflict().
// Use the new values that were set on create.
UpdateNewValues().
Exec(ctx)

id, err := client.User.
Create().
SetAge(30).
SetName("Ariel").
OnConflict().
// Use the "age" that was set on create.
UpdateAge().
// Set a different "name" in case of conflict.
SetName("Mashraki").
ID(ctx)

// Customize the UPDATE clause.
err := client.User.
Create().
SetAge(30).
SetName("Ariel").
OnConflict().
UpdateNewValues().
// Override some of the fields with a custom update.
Update(func(u *ent.UserUpsert) {
u.SetAddress("localhost")
u.AddCount(1)
u.ClearPhone()
}).
Exec(ctx)

在 PostgreSQL中需要使用 冲突目标

// Setting the column names using the fluent API.
err := client.User.
Create().
SetName("Ariel").
OnConflictColumns(user.FieldName).
UpdateNewValues().
Exec(ctx)

// Setting the column names using the SQL API.
err := client.User.
Create().
SetName("Ariel").
OnConflict(
sql.ConflictColumns(user.FieldName),
).
UpdateNewValues().
Exec(ctx)

// Setting the constraint name using the 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 ...
信息

由于更新接口是通过 ON CONFLICT 子语句(以及 MySQL 中的 ON DUPLICATE KEY 子句)实现的,Ent 仅向数据库执行一条语句,因此对于此类操作仅是创建 钩子

更新多插入多个实体

err := client.User.             // UserClient
CreateBulk(builders...). // User bulk create.
OnConflict(). // User bulk upsert.
UpdateNewValues(). // Use the values that were set on create in case of conflict.
Exec(ctx) // Execute the statement.

查询图

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

users, err := client.User.		// UserClient.
Query(). // User query builder.
Where(user.HasFollowers()). // filter only users with followers.
All(ctx) // query and return.

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

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 allows selecting one or more fields (columns) of the returned entity.
// The default is selecting all fields defined in the entity schema.
Select(pet.FieldName).
Save(ctx)

删除一个实体

删除一个实体:

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

通过 ID 删除一个实体:

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

有条件的删除一个实体

在某些项目中,禁止执行“删除多个”操作,并通过钩子进行拦截。但仍有根据 ID 删除单个实体的需求,同时确保其满足特定条件。 此时可使用 Where 选项,具体如下:

err := client.Todo.
DeleteOneID(id).
Where(
// Allow deleting only expired todos.
todo.ExpireLT(time.Now()),
).
Exec(ctx)
switch {
// If the entity does not meet a specific condition,
// the operation will return an "ent.NotFoundError".
case ent.IsNotFound(err):
fmt.Println("todo item was not found")
// Any other error.
case err != nil:
fmt.Println("deletion error:", err)
}

删除多个实体

使用谓词删除多个实体:

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

突变(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 sets the age and the name for any mutation.
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 wraps the 2 methods for getting
// and setting the "name" field in mutations.
type SetNamer interface {
SetName(string)
Name() (string, bool)
}

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