Eager Loading
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:
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:
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 feature-flag and seamlessly integrated with EntGQL Fields Collection.
- Ent
- GraphQL
See the GraphQL tab to learn more about the motivation behind this 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(...)
// 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
}
}
An example of a GraphQL query that has two aliases referencing the same edge with different arguments.
query {
posts {
id
title
published: comments(where: { status: PUBLISHED }) {
edges {
node {
text
}
}
}
draft: comments(where: { status: DRAFT }) {
edges {
node {
text
}
}
}
}
}
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.