图遍历 (Traversals)
为演示需要,我们将生成以下图结构:

第一步是生成三个模式:Pet、User、Group。
go run -mod=mod entgo.io/ent/cmd/ent new Pet User Group
为模式添加必要的字段和边:
ent/schema/pet.go
// Pet 持有 Pet 实体的模式定义。
type Pet struct {
ent.Schema
}
// Pet 的字段。
func (Pet) Fields() []ent.Field {
return []ent.Field{
field.String("name"),
}
}
// Pet 的边。
func (Pet) Edges() []ent.Edge {
return []ent.Edge{
edge.To("friends", Pet.Type),
edge.From("owner", User.Type).
Ref("pets").
Unique(),
}
}
ent/schema/user.go
// User 持有 User 实体的模式定义。
type User struct {
ent.Schema
}
// User 的字段。
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int("age"),
field.String("name"),
}
}
// User 的边。
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type),
edge.To("friends", User.Type),
edge.From("groups", Group.Type).
Ref("users"),
edge.From("manage", Group.Type).
Ref("admin"),
}
}
ent/schema/group.go
// Group 持有 Group 实体的模式定义。
type Group struct {
ent.Schema
}
// Group 的字段。
func (Group) Fields() []ent.Field {
return []ent.Field{
field.String("name"),
}
}
// Group 的边。
func (Group) Edges() []ent.Edge {
return []ent.Edge{
edge.To("users", User.Type),
edge.To("admin", User.Type).
Unique(),
}
}
编写填充图顶点和边的代码:
func Gen(ctx context.Context, client *ent.Client) error {
hub, err := client.Group.
Create().
SetName("Github").
Save(ctx)
if err != nil {
return fmt.Errorf("failed creating the group: %w", err)
}
// 创建群组管理员。
// 与 `Save` 不同,`SaveX` 在出错时会触发 panic。
dan := client.User.
Create().
SetAge(29).
SetName("Dan").
AddManage(hub).
SaveX(ctx)
// 创建 "Ariel" 及其宠物。
a8m := client.User.
Create().
SetAge(30).
SetName("Ariel").
AddGroups(hub).
AddFriends(dan).
SaveX(ctx)
pedro := client.Pet.
Create().
SetName("Pedro").
SetOwner(a8m).
SaveX(ctx)
xabi := client.Pet.
Create().
SetName("Xabi").
SetOwner(a8m).
SaveX(ctx)
// 创建 "Alex" 及其宠物。
alex := client.User.
Create().
SetAge(37).
SetName("Alex").
SaveX(ctx)
coco := client.Pet.
Create().
SetName("Coco").
SetOwner(alex).
AddFriends(pedro).
SaveX(ctx)
fmt.Println("Pets created:", pedro, xabi, coco)
// 输出:
// Pets created: Pet(id=1, name=Pedro) Pet(id=2, name=Xabi) Pet(id=3, name=Coco)
return nil
}
接下来演示几个遍历操作及其代码实现:

上图遍历从 Group 实体开始,继续到其 admin(边),
继续到其 friends(边),获取其 pets(边),获取每个宠物的 friends(边),
最后请求它们的所有者。
func Traverse(ctx context.Context, client *ent.Client) error {
owner, err := client.Group. // GroupClient.
Query(). // 查询构建器。
Where(group.Name("Github")). // 仅筛选 Github 群组(唯一)。
QueryAdmin(). // 获取 Dan。
QueryFriends(). // 获取 Dan 的好友:[Ariel]。
QueryPets(). // 他们的宠物:[Pedro, Xabi]。
QueryFriends(). // Pedro 的好友:[Coco],Xabi 的好友:[]。
QueryOwner(). // Coco 的所有者:Alex。
Only(ctx) // 期望查询只返回一个实体。
if err != nil {
return fmt.Errorf("failed querying the owner: %w", err)
}
fmt.Println(owner)
// 输出:
// User(id=3, age=37, name=Alex)
return nil
}
那么以下遍历又如何实现呢?

我们需要获取所有拥有主人(边)的宠物(实体),且该主人是某个群组管理员(边)的好友(边)。
func Traverse(ctx context.Context, client *ent.Client) error {
pets, err := client.Pet.
Query().
Where(
pet.HasOwnerWith(
user.HasFriendsWith(
user.HasManage(),
),
),
).
All(ctx)
if err != nil {
return fmt.Errorf("failed querying the pets: %w", err)
}
fmt.Println(pets)
// 输出:
// [Pet(id=1, name=Pedro) Pet(id=2, name=Xabi)]
return nil
}
完整示例参见 GitHub。