增删改查接口
就像在 代码生成介绍 中提到的,在模式上运行 ent会生成如下资产:
- 用来与图互操作的
Client和Tx对象。 - 每个模式类型的 CRUD 构建器。
- 每个模式类型的实体对象(Go 语言结构体)。
- 与构建器互操作的包含常量和谓语的包。
- SQL 方言的
migrate包,查阅 迁移 了解更多信息。
创建客户端
- SQLite
- PostgreSQL
- MySQL
- Gremlin (AWS Neptune)
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)
}
}
package main
import (
"context"
"log"
"entdemo/ent"
_ "github.com/lib/pq"
)
func main() {
client, err := ent.Open("postgres","host=<host> port=<port> user=<user> dbname=<database> password=<pass>")
if err != nil {
log.Fatalf("failed opening connection to postgres: %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)
}
}
package main
import (
"context"
"log"
"entdemo/ent"
_ "github.com/go-sql-driver/mysql"
)
func main() {
client, err := ent.Open("mysql", "<user>:<pass>@tcp(<host>:<port>)/<database>?parseTime=True")
if err != nil {
log.Fatalf("failed opening connection to mysql: %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)
}
}
package main
import (
"log"
"entdemo/ent"
)
func main() {
client, err := ent.Open("gremlin", "http://localhost:8182")
if err != nil {
log.Fatal(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 选项,具体如下:
- 通过ID更新
- 通过实体更新
- 直接更新
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)
}
err := client.Todo.
UpdateOne(node).
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)
}
firstTodo, err = firstTodo.
Update().
SetStatus(todo.StatusDone).
AddVersion(1).
Where(
// Ensure the current version matches the one in the database.
todo.Version(firstTodo.Version),
).
Save(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)
选择部分对象及其关联关系。获取所有宠物及其主人,但仅选择并填充 ID 和 Name 字段。
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.UserCreate 和 ent.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)
}
}