增删改查 API
正如 介绍 部分所述,对 schemas 运行 ent
命令, 将生成以下资源:
Client
和Tx
对象用于与图的交互。- 每个schema对应的增删改查生成器, 查看 CRUD 了解更多信息。
- 每个schema的实体对象(Go结构体)。
- 含常量和查询条件的包,用于与生成器交互。
- 用于数据迁移的
migrate
包. 查看 迁移 获取更多信息。
创建新的客户端
MySQL
package main
import (
"log"
"<project>/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.Fatal(err)
}
defer client.Close()
}
PostgreSQL
package main
import (
"log"
"<project>/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.Fatal(err)
}
defer client.Close()
}
SQLite
package main
import (
"log"
"<project>/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.Fatal(err)
}
defer client.Close()
}
Gremlin (AWS Neptune)
package main
import (
"log"
"<project>/ent"
)
func main() {
client, err := ent.Open("gremlin", "http://localhost:8182")
if err != nil {
log.Fatal(err)
}
}
创建一个实体
通过 Save 保存一个用户.
a8m, err := client.User. // UserClient.
Create(). // 用户创建构造器
SetName("a8m"). // 设置字段的值
SetNillableAge(age). // 忽略nil检查
AddGroups(g1, g2). // 添加多个边
SetSpouse(nati). // 设置单个边
Save(ctx) // 创建并返回
通过 SaveX 保存一个宠物; 和 Save 不一样, SaveX 在出错时 panic。
pedro := client.Pet. // PetClient.
Create(). // 宠物创建构造器
SetName("pedro"). // 设置字段的值
SetOwner(a8m). // 设置主人 (唯一的边)
SaveX(ctx) // 创建并返回
批量创建
通过 Save 批量保存宠物。
names := []string{"pedro", "xabi", "layla"}
bulk := make([]*ent.PetCreate, len(names))
for i, name := range names {
bulk[i] = client.Pet.Create().SetName(name).SetOwner(a8m)
}
pets, err := client.Pet.CreateBulk(bulk...).Save(ctx)
更新单个实体
更新一个从数据库内的实体。
a8m, err = a8m.Update(). // 用户更新构造器
RemoveGroup(g2). // 移除特定的边
ClearCard(). // 清空唯一的边
SetAge(30). // 设置字段的值
Save(ctx) // 保存并返回
通过ID更新
pedro, err := client.Pet. // PetClient.
UpdateOneID(id). // 宠物更新构造器
SetName("pedro"). // 设置名字字段
SetOwnerID(owner). // 通过ID设置唯一的边
Save(ctx) // 保存并返回
批量更新
通过断言筛选。
n, err := client.User. // UserClient.
Update(). // 宠物更新构造器
Where( //
user.Or( // (age >= 30 OR name = "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( // age = 20
user.Age(20), // OR
user.Age(30), // age = 30
) // )
), //
). //
SetName("a8m"). // 设置名字字段
Save(ctx) // 执行并返回
合并单个实体
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)
// 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)
In order to customize the executed statement, use the 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 ...
Since the upsert API is implemented using the ON CONFLICT
clause (and ON DUPLICATE KEY
in MySQL), Ent executes only one statement to the database, and therefore, only create hooks are applied for such operations. :::" class="reference-link">Since the upsert API is implemented using the ON CONFLICT
clause (and ON DUPLICATE KEY
in MySQL), Ent executes only one statement to the database, and therefore, only create hooks are applied for such operations. :::
合并多个实体
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.
在图中查询
Get all users with followers.
users, err := client.User. // UserClient.
Query(). // User query builder.
Where(user.HasFollowers()). // filter only users with followers.
All(ctx) // query and return.
Get all followers of a specific user; Start the traversal from a node in the graph.
users, err := a8m.
QueryFollowers().
All(ctx)
Get all pets of the followers of a user.
users, err := a8m.
QueryFollowers().
QueryPets().
All(ctx)
Count the number of posts without comments.
n, err := client.Post.
Query().
Where(
post.Not(
post.HasComments(),
)
).
Count(ctx)
More advance traversals can be found in the next section.
字段选择
Get all pet names.
names, err := client.Pet.
Query().
Select(pet.FieldName).
Strings(ctx)
Get all unique pet names.
names, err := client.Pet.
Query().
Unique(true).
Select(pet.FieldName).
Strings(ctx)
Count the number of unique pet names.
n, err := client.Pet.
Query().
Unique(true).
Select(pet.FieldName).
Count(ctx)
Select partial objects and partial associations.gs Get all pets and their owners, but select and fill only the ID
and Name
fields.
pets, err := client.Pet.
Query().
Select(pet.FieldName).
WithOwner(func (q *ent.UserQuery) {
q.Select(user.FieldName)
}).
All(ctx)
Scan all pet names and ages to custom struct.
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)
}
Update an entity and return a partial of it.
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)
删除一个
Delete an entity.
err := client.User.
DeleteOne(a8m).
Exec(ctx)
Delete by ID.
err := client.User.
DeleteOneID(id).
Exec(ctx)
批量删除
Delete using predicates.
_, err := client.File.
Delete().
Where(file.UpdatedAtLT(date)).
Exec(ctx)
更变
Each generated node type has its own type of mutation. For example, all User builders, share the same generated UserMutation
object. However, all builder types implement the generic ent.Mutation interface.
For example, in order to write a generic code that apply a set of methods on both ent.UserCreate
and ent.UserUpdate
, use the UserMutation
object:
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")
}
In some cases, you want to apply a set of methods on multiple types. For cases like this, either use the generic ent.Mutation
interface, or create your own interface.
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)
}
}