跳到主要内容

增删改查 API

正如 介绍 部分所述,对 schemas 运行 ent 命令, 将生成以下资源:

  • ClientTx 对象用于与图的交互。
  • 每个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) // 执行并返回

Upsert One#

Ent supports upsert records using the sql/upsert feature-flag.

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")
}).
Exec(ctx)

In PostgreSQL, the conflict target is required:

// 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.UpadteName()
}).
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. :::

Upsert Many#

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.

Query The Graph#

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)

More advance traversals can be found in the next section.

Field Selection#

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)

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 One#

Delete an entity.

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

Delete by ID.

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

Delete Many#

Delete using predicates.

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

Mutation#

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)
}
}