snapshot of https://github.com/ent/ent.git (master, 2026-09-15; blobs > 131072B omitted)
This commit is contained in:
192
doc/md/eager-load.mdx
Normal file
192
doc/md/eager-load.mdx
Normal file
@@ -0,0 +1,192 @@
|
||||
---
|
||||
id: eager-load
|
||||
title: Eager Loading
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
## Overview
|
||||
|
||||
`ent` supports querying entities with their associations (through their edges). The associated entities
|
||||
are populated to the `Edges` field in the returned object.
|
||||
|
||||
Let's give an example of what the API looks like for the following schema:
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
**Query all users with their pets:**
|
||||
```go
|
||||
users, err := client.User.
|
||||
Query().
|
||||
WithPets().
|
||||
All(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// The returned users look as follows:
|
||||
//
|
||||
// [
|
||||
// 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)
|
||||
// Output:
|
||||
// User(...) -> Pet(...)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Eager loading allows to query more than one association (including nested), and also
|
||||
filter, sort or limit their result. For example:
|
||||
|
||||
```go
|
||||
admins, err := client.User.
|
||||
Query().
|
||||
Where(user.Admin(true)).
|
||||
// Populate the `pets` that associated with the `admins`.
|
||||
WithPets().
|
||||
// Populate the first 5 `groups` that associated with the `admins`.
|
||||
WithGroups(func(q *ent.GroupQuery) {
|
||||
q.Limit(5) // Limit to 5.
|
||||
q.WithUsers() // Populate the `users` of each `groups`.
|
||||
}).
|
||||
All(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The returned users look as follows:
|
||||
//
|
||||
// [
|
||||
// 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)
|
||||
// Output:
|
||||
// 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)
|
||||
// Output:
|
||||
// Admin(...) -> Group(...) -> User(...)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
Each query-builder has a list of methods in the form of `With<E>(...func(<N>Query))` for each of its edges.
|
||||
`<E>` stands for the edge name (like, `WithGroups`) and `<N>` for the edge type (like, `GroupQuery`).
|
||||
|
||||
Note that only SQL dialects support this feature.
|
||||
|
||||
## Named Edges
|
||||
|
||||
In some cases there is a need for preloading edges with custom names. For example, a GraphQL query that has two aliases
|
||||
referencing the same edge with different arguments. For this situation, Ent provides another API named `WithNamed<E>`
|
||||
that can be enabled using the [`namedges`](features.md#named-edges) feature-flag and seamlessly integrated with
|
||||
[EntGQL Fields Collection](tutorial-todo-gql-field-collection.md).
|
||||
|
||||
<Tabs
|
||||
defaultValue="ent"
|
||||
values={[
|
||||
{label: 'Ent', value: 'ent'},
|
||||
{label: 'GraphQL', value: 'graphql'},
|
||||
]}>
|
||||
<TabItem value="ent">
|
||||
|
||||
See the GraphQL tab to learn more about the motivation behind this API.
|
||||
|
||||
```go
|
||||
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(...)
|
||||
|
||||
// Get the preloaded edges by their name:
|
||||
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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="graphql">
|
||||
|
||||
An example of a GraphQL query that has two aliases referencing the same edge with different arguments.
|
||||
|
||||
```graphql
|
||||
query {
|
||||
posts {
|
||||
id
|
||||
title
|
||||
published: comments(where: { status: PUBLISHED }) {
|
||||
edges {
|
||||
node {
|
||||
text
|
||||
}
|
||||
}
|
||||
}
|
||||
draft: comments(where: { status: DRAFT }) {
|
||||
edges {
|
||||
node {
|
||||
text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Implementation
|
||||
|
||||
Since an Ent query can eager-load more than one edge, it is not possible to load all associations in a single
|
||||
`JOIN` operation. Therefore, Ent executes additional query to load each association. This expected to be optimized
|
||||
in future versions.
|
||||
Reference in New Issue
Block a user