预加载 (Eager Loading)
概述
ent 支持查询实体及其关联(通过边)。关联的实体将被填充到返回对象的 Edges 字段中。
以下是一个 API 示例,基于以下架构:

查询所有用户及其宠物:
users, err := client.User.
Query().
WithPets().
All(ctx)
if err != nil {
return err
}
// 返回的用户结构如下:
//
// [
// User {
// ID: 1,
// Name: "a8m",
// Edges: {
// Pets: [Pet(...), ...]
// ...
// }
// },
// ...
// ]
//
for _, u := range users {
for _, p := range u.Edges.Pets {
fmt.Printf("User(%v) -> Pet(%v)\n", u.ID, p.ID)
// 输出:
// User(...) -> Pet(...)
}
}
预加载支持查询多个关联(包括嵌套关联),并可以对结果进行过滤、排序或限制。例如:
admins, err := client.User.
Query().
Where(user.Admin(true)).
// 加载与 `admins` 关联的 `pets`。
WithPets().
// 加载与 `admins` 关联的前 5 个 `groups`。
WithGroups(func(q *ent.GroupQuery) {
q.Limit(5) // 限制为 5 个。
q.WithUsers() // 加载每个 `groups` 的 `users`。
}).
All(ctx)
if err != nil {
return err
}
// 返回的用户结构如下:
//
// [
// User {
// ID: 1,
// Name: "admin1",
// Edges: {
// Pets: [Pet(...), ...]
// Groups: [
// Group {
// ID: 7,
// Name: "GitHub",
// Edges: {
// Users: [User(...), ...]
// ...
// }
// }
// ]
// }
// },
// ...
// ]
//
for _, admin := range admins {
for _, p := range admin.Edges.Pets {
fmt.Printf("Admin(%v) -> Pet(%v)\n", u.ID, p.ID)
// 输出:
// Admin(...) -> Pet(...)
}
for _, g := range admin.Edges.Groups {
for _, u := range g.Edges.Users {
fmt.Printf("Admin(%v) -> Group(%v) -> User(%v)\n", u.ID, g.ID, u.ID)
// 输出:
// Admin(...) -> Group(...) -> User(...)
}
}
}
API
每个查询构建器都有一组以 With<E>(...func(<N>Query)) 形式命名的方法,用于处理其边。
<E> 表示边名称(例如 WithGroups),<N> 表示边的类型(例如 GroupQuery)。
注意:此功能仅支持 SQL 方言。
命名边
在某些情况下,需要以自定义名称预加载边。例如,一个 GraphQL 查询有两个别名,它们引用同一个边但参数不同。针对这种情况,Ent 提供了另一个名为 WithNamed<E> 的 API,可通过 namedges 功能标志启用,并可无缝集成到 EntGQL 字段收集 中。
- Ent
- GraphQL
请参阅 GraphQL 标签页以了解此 API 背后的动机。
posts, err := client.Post.Query().
WithNamedComments("published", func(q *ent.CommentQuery) {
q.Where(comment.StatusEQ(comment.StatusPublished))
})
WithNamedComments("draft", func(q *ent.CommentQuery) {
q.Where(comment.StatusEQ(comment.StatusDraft))
}).
Paginate(...)
// 按名称获取预加载的边:
for _, p := range posts {
published, err := p.Edges.NamedComments("published")
if err != nil {
return err
}
draft, err := p.Edges.NamedComments("draft")
if err != nil {
return err
}
}
一个 GraphQL 查询示例,该查询有两个别名,引用同一个边但参数不同。
query {
posts {
id
title
published: comments(where: { status: PUBLISHED }) {
edges {
node {
text
}
}
}
draft: comments(where: { status: DRAFT }) {
edges {
node {
text
}
}
}
}
}
实现
由于一个 Ent 查询可以预加载多个边,因此不可能在单个 JOIN 操作中加载所有关联。因此,Ent 会执行额外的查询来加载每个关联。此问题预计将在未来版本中优化。