概述

边是实体之间的关系(或者关联)。 例如,用户所拥有的(多只)宠物,或者用户组所关联的(多个)用户。

er-group-users

在上面的例子中,你可以看到使用边声明的2种关系。 让我们接着往下看。

  1. pets / owner 边;用户的宠物和宠物的主人 -

ent/schema/user.go

  1. package schema
  2. import (
  3. "entgo.io/ent"
  4. "entgo.io/ent/schema/edge"
  5. )
  6. // User schema.
  7. type User struct {
  8. ent.Schema
  9. }
  10. // Fields of the user.
  11. func (User) Fields() []ent.Field {
  12. return []ent.Field{
  13. // ...
  14. }
  15. }
  16. // Edges of the user.
  17. func (User) Edges() []ent.Edge {
  18. return []ent.Edge{
  19. edge.To("pets", Pet.Type),
  20. }
  21. }

ent/schema/pet.go

  1. package schema
  2. import (
  3. "entgo.io/ent"
  4. "entgo.io/ent/schema/edge"
  5. )
  6. // Pet holds the schema definition for the Pet entity.
  7. type Pet struct {
  8. ent.Schema
  9. }
  10. // Fields of the Pet.
  11. func (Pet) Fields() []ent.Field {
  12. return []ent.Field{
  13. // ...
  14. }
  15. }
  16. // Edges of the Pet.
  17. func (Pet) Edges() []ent.Edge {
  18. return []ent.Edge{
  19. edge.From("owner", User.Type).
  20. Ref("pets").
  21. Unique(),
  22. }
  23. }

你可以看到,一个 User 实体可以拥有 多个 宠物,但是一个 Pet 实体只能拥有 一名 主人。
在关系定义中,pets 边是一个 O2M (一对多)的关系,owner 边是一个 M2O (多对一)的关系。

User 实体拥有 pets/owner 的关系因为使用了 edge.ToPet 实体则仅有一个反向引用,是靠 edge.From 以及 Ref 方法实现。

Ref 方法明确了在 User 实体中我们所引用的那条边,因为存在从一个实体到其他实体多条引用的场景。

边/关系的数量可以使用 Unique 方法加以约束, 后续将作更多介绍。

  1. users / groups 边; 用户组的用户和用户所属的用户组 -

ent/schema/group.go

  1. package schema
  2. import (
  3. "entgo.io/ent"
  4. "entgo.io/ent/schema/edge"
  5. )
  6. // Group schema.
  7. type Group struct {
  8. ent.Schema
  9. }
  10. // Fields of the group.
  11. func (Group) Fields() []ent.Field {
  12. return []ent.Field{
  13. // ...
  14. }
  15. }
  16. // Edges of the group.
  17. func (Group) Edges() []ent.Edge {
  18. return []ent.Edge{
  19. edge.To("users", User.Type),
  20. }
  21. }

ent/schema/user.go

  1. package schema
  2. import (
  3. "entgo.io/ent"
  4. "entgo.io/ent/schema/edge"
  5. )
  6. // User schema.
  7. type User struct {
  8. ent.Schema
  9. }
  10. // Fields of the user.
  11. func (User) Fields() []ent.Field {
  12. return []ent.Field{
  13. // ...
  14. }
  15. }
  16. // Edges of the user.
  17. func (User) Edges() []ent.Edge {
  18. return []ent.Edge{
  19. edge.From("groups", Group.Type).
  20. Ref("users"),
  21. // "pets" declared in the example above.
  22. edge.To("pets", Pet.Type),
  23. }
  24. }

如你所见,一个用户组能拥有多名用户,同时一名用户能属于多个用户组。
在定义关系时,users的边是一个M2M (多对多)关系,同时groups 边同样也是一个M2M(多对多)关系。

To 和 From

edge.Toedge.From 是两个用于创建边/关系的生成器。

一个实体使用 edge.To 生成器定义一条边以构建关系模式,和使用 edge.From 生成器仅定义一个反向引用关系完全是两回事(命名不同)。

让我们通过一些例子来看看如何使用边定义不同的关系模式。

关系

一对一( O2O)两者之间

er-user-card

在本例中,一名用户 仅有一张 信用卡,同时一张卡 只属一名 用户。

User 实体基于 edge.To 定义了名下的卡并将该引用关系命名 card,同时 Card 实体基于 edge.From 定义了一个相对的反向引用并命名 owner

ent/schema/user.go

  1. // Edges of the user.
  2. func (User) Edges() []ent.Edge {
  3. return []ent.Edge{
  4. edge.To("card", Card.Type).
  5. Unique(),
  6. }
  7. }

ent/schema/card.go

  1. // Edges of the Card.
  2. func (Card) Edges() []ent.Edge {
  3. return []ent.Edge{
  4. edge.From("owner", User.Type).
  5. Ref("card").
  6. Unique().
  7. // We add the "Required" method to the builder
  8. // to make this edge required on entity creation.
  9. // i.e. Card cannot be created without its owner.
  10. Required(),
  11. }
  12. }

与这些边的交互API如下:

  1. func Do(ctx context.Context, client *ent.Client) error {
  2. a8m, err := client.User.
  3. Create().
  4. SetAge(30).
  5. SetName("Mashraki").
  6. Save(ctx)
  7. if err != nil {
  8. return fmt.Errorf("creating user: %w", err)
  9. }
  10. log.Println("user:", a8m)
  11. card1, err := client.Card.
  12. Create().
  13. SetOwner(a8m).
  14. SetNumber("1020").
  15. SetExpired(time.Now().Add(time.Minute)).
  16. Save(ctx)
  17. if err != nil {
  18. return fmt.Errorf("creating card: %w", err)
  19. }
  20. log.Println("card:", card1)
  21. // Only returns the card of the user,
  22. // and expects that there's only one.
  23. card2, err := a8m.QueryCard().Only(ctx)
  24. if err != nil {
  25. return fmt.Errorf("querying card: %w", err)
  26. }
  27. log.Println("card:", card2)
  28. // The Card entity is able to query its owner using
  29. // its back-reference.
  30. owner, err := card2.QueryOwner().Only(ctx)
  31. if err != nil {
  32. return fmt.Errorf("querying owner: %w", err)
  33. }
  34. log.Println("owner:", owner)
  35. return nil
  36. }

完整示例请参考 GitHub

一对一(O2O)自引用

er-linked-list

在这个链表的例子中(Linked-List),我们有一个 递归关系 名为 next/prev。 表中的每个结点 仅能拥有一个 next 结点。 如果结点A链到(使用 next)结点B,B即可使用 prev (反向关系引用)。

ent/schema/node.go

  1. // Edges of the Node.
  2. func (Node) Edges() []ent.Edge {
  3. return []ent.Edge{
  4. edge.To("next", Node.Type).
  5. Unique().
  6. From("prev").
  7. Unique(),
  8. }
  9. }

如你所见,在自引用的关系中,你可以把边和它的引用在同一个构造器中声明。

  1. func (Node) Edges() []ent.Edge {
  2. return []ent.Edge{
  3. + edge.To("next", Node.Type).
  4. + Unique().
  5. + From("prev").
  6. + Unique(),
  7. - edge.To("next", Node.Type).
  8. - Unique(),
  9. - edge.From("prev", Node.Type).
  10. - Ref("next).
  11. - Unique(),
  12. }
  13. }

与这些边交互的 API 如下:

  1. func Do(ctx context.Context, client *ent.Client) error {
  2. head, err := client.Node.
  3. Create().
  4. SetValue(1).
  5. Save(ctx)
  6. if err != nil {
  7. return fmt.Errorf("creating the head: %w", err)
  8. }
  9. curr := head
  10. // Generate the following linked-list: 1<->2<->3<->4<->5.
  11. for i := 0; i < 4; i++ {
  12. curr, err = client.Node.
  13. Create().
  14. SetValue(curr.Value + 1).
  15. SetPrev(curr).
  16. Save(ctx)
  17. if err != nil {
  18. return err
  19. }
  20. }
  21. // Loop over the list and print it. `FirstX` panics if an error occur.
  22. for curr = head; curr != nil; curr = curr.QueryNext().FirstX(ctx) {
  23. fmt.Printf("%d ", curr.Value)
  24. }
  25. // Output: 1 2 3 4 5
  26. // Make the linked-list circular:
  27. // The tail of the list, has no "next".
  28. tail, err := client.Node.
  29. Query().
  30. Where(node.Not(node.HasNext())).
  31. Only(ctx)
  32. if err != nil {
  33. return fmt.Errorf("getting the tail of the list: %v", tail)
  34. }
  35. tail, err = tail.Update().SetNext(head).Save(ctx)
  36. if err != nil {
  37. return err
  38. }
  39. // Check that the change actually applied:
  40. prev, err := head.QueryPrev().Only(ctx)
  41. if err != nil {
  42. return fmt.Errorf("getting head's prev: %w", err)
  43. }
  44. fmt.Printf("\n%v", prev.Value == tail.Value)
  45. // Output: true
  46. return nil
  47. }

完整示例请参考 GitHub

一对一(O2O)双向自引用

er-user-spouse

在这个用户配偶的例子中,我们定义了一个 对称的一对一(O2O)关系 名为 spouse。 每名用户仅能有一名配偶。 如果A将其配偶设置成(使用 spouse)B,B则可使用spouse 边查到其相应的配偶。

注意在双向自引用场景下没有所有者/反向引用关系。

ent/schema/user.go

  1. // Edges of the User.
  2. func (User) Edges() []ent.Edge {
  3. return []ent.Edge{
  4. edge.To("spouse", User.Type).
  5. Unique(),
  6. }
  7. }

与这些边的交互API如下:

  1. func Do(ctx context.Context, client *ent.Client) error {
  2. a8m, err := client.User.
  3. Create().
  4. SetAge(30).
  5. SetName("a8m").
  6. Save(ctx)
  7. if err != nil {
  8. return fmt.Errorf("creating user: %w", err)
  9. }
  10. nati, err := client.User.
  11. Create().
  12. SetAge(28).
  13. SetName("nati").
  14. SetSpouse(a8m).
  15. Save(ctx)
  16. if err != nil {
  17. return fmt.Errorf("creating user: %w", err)
  18. }
  19. // Query the spouse edge.
  20. // Unlike `Only`, `OnlyX` panics if an error occurs.
  21. spouse := nati.QuerySpouse().OnlyX(ctx)
  22. fmt.Println(spouse.Name)
  23. // Output: a8m
  24. spouse = a8m.QuerySpouse().OnlyX(ctx)
  25. fmt.Println(spouse.Name)
  26. // Output: nati
  27. // Query how many users have a spouse.
  28. // Unlike `Count`, `CountX` panics if an error occurs.
  29. count := client.User.
  30. Query().
  31. Where(user.HasSpouse()).
  32. CountX(ctx)
  33. fmt.Println(count)
  34. // Output: 2
  35. // Get the user, that has a spouse with name="a8m".
  36. spouse = client.User.
  37. Query().
  38. Where(user.HasSpouseWith(user.Name("a8m"))).
  39. OnlyX(ctx)
  40. fmt.Println(spouse.Name)
  41. // Output: nati
  42. return nil
  43. }

注意,外键列可以配置并暴露为实体字段,如下使用边字段选项:

  1. // Fields of the User.
  2. func (User) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.Int("spouse_id").
  5. Optional(),
  6. }
  7. }
  8. // Edges of the User.
  9. func (User) Edges() []ent.Edge {
  10. return []ent.Edge{
  11. edge.To("spouse", User.Type).
  12. Unique().
  13. Field("spouse_id"),
  14. }
  15. }

完整示例请参考 GitHub

一对多(O2M )两者之间

er-user-pets

在这个用户和宠物例子中,我们在用户与其宠物之间定义了一个一对多(O2M)的关系。 每个用户能够拥有多个宠物,一个宠物只能拥有一个主人。 如果用户 A 使用 pets 边添加了一个宠物 B,B 可以使用 owner 边(反向引用的边)获得它的主人。

注意:在 Pet 结构 (Schema) 的视角看,也是一个多对一(M2O)的关系。

ent/schema/user.go

  1. // Edges of the User.
  2. func (User) Edges() []ent.Edge {
  3. return []ent.Edge{
  4. edge.To("pets", Pet.Type),
  5. }
  6. }

ent/schema/pet.go

  1. // Edges of the Pet.
  2. func (Pet) Edges() []ent.Edge {
  3. return []ent.Edge{
  4. edge.From("owner", User.Type).
  5. Ref("pets").
  6. Unique(),
  7. }
  8. }

与这些边的交互API如下:

  1. func Do(ctx context.Context, client *ent.Client) error {
  2. // Create the 2 pets.
  3. pedro, err := client.Pet.
  4. Create().
  5. SetName("pedro").
  6. Save(ctx)
  7. if err != nil {
  8. return fmt.Errorf("creating pet: %w", err)
  9. }
  10. lola, err := client.Pet.
  11. Create().
  12. SetName("lola").
  13. Save(ctx)
  14. if err != nil {
  15. return fmt.Errorf("creating pet: %w", err)
  16. }
  17. // Create the user, and add its pets on the creation.
  18. a8m, err := client.User.
  19. Create().
  20. SetAge(30).
  21. SetName("a8m").
  22. AddPets(pedro, lola).
  23. Save(ctx)
  24. if err != nil {
  25. return fmt.Errorf("creating user: %w", err)
  26. }
  27. fmt.Println("User created:", a8m)
  28. // Output: User(id=1, age=30, name=a8m)
  29. // Query the owner. Unlike `Only`, `OnlyX` panics if an error occurs.
  30. owner := pedro.QueryOwner().OnlyX(ctx)
  31. fmt.Println(owner.Name)
  32. // Output: a8m
  33. // Traverse the sub-graph. Unlike `Count`, `CountX` panics if an error occurs.
  34. count := pedro.
  35. QueryOwner(). // a8m
  36. QueryPets(). // pedro, lola
  37. CountX(ctx) // count
  38. fmt.Println(count)
  39. // Output: 2
  40. return nil
  41. }

注意,外键列可以配置并暴露为实体字段,如下使用 Edge Field 选项:

  1. // Fields of the Pet.
  2. func (Pet) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.Int("owner_id").
  5. Optional(),
  6. }
  7. }
  8. // Edges of the Pet.
  9. func (Pet) Edges() []ent.Edge {
  10. return []ent.Edge{
  11. edge.From("owner", User.Type).
  12. Ref("pets").
  13. Unique().
  14. Field("owner_id"),
  15. }
  16. }

完整示例请参考 GitHub

一对多(O2M )自引用

er-tree

在这个例子中,我们在树结点及其子结点(或它们的父结点)间定义了一个一对多(O2M)递归关系。
树中的每个结点 有多个 子结点,以及 一个 父结点。 如果结点A将B添加到其子结点, B即可使用 owner 边查到其所有者。

ent/schema/node.go

  1. // Edges of the Node.
  2. func (Node) Edges() []ent.Edge {
  3. return []ent.Edge{
  4. edge.To("children", Node.Type).
  5. From("parent").
  6. Unique(),
  7. }
  8. }

如你所见,在自引用的关系中,你可以把边和它的引用在同一个构造器中声明。

  1. func (Node) Edges() []ent.Edge {
  2. return []ent.Edge{
  3. + edge.To("children", Node.Type).
  4. + From("parent").
  5. + Unique(),
  6. - edge.To("children", Node.Type),
  7. - edge.From("parent", Node.Type).
  8. - Ref("children").
  9. - Unique(),
  10. }
  11. }

与这些边的交互API如下:

  1. func Do(ctx context.Context, client *ent.Client) error {
  2. root, err := client.Node.
  3. Create().
  4. SetValue(2).
  5. Save(ctx)
  6. if err != nil {
  7. return fmt.Errorf("creating the root: %w", err)
  8. }
  9. // Add additional nodes to the tree:
  10. //
  11. // 2
  12. // / \
  13. // 1 4
  14. // / \
  15. // 3 5
  16. //
  17. // Unlike `Save`, `SaveX` panics if an error occurs.
  18. n1 := client.Node.
  19. Create().
  20. SetValue(1).
  21. SetParent(root).
  22. SaveX(ctx)
  23. n4 := client.Node.
  24. Create().
  25. SetValue(4).
  26. SetParent(root).
  27. SaveX(ctx)
  28. n3 := client.Node.
  29. Create().
  30. SetValue(3).
  31. SetParent(n4).
  32. SaveX(ctx)
  33. n5 := client.Node.
  34. Create().
  35. SetValue(5).
  36. SetParent(n4).
  37. SaveX(ctx)
  38. fmt.Println("Tree leafs", []int{n1.Value, n3.Value, n5.Value})
  39. // Output: Tree leafs [1 3 5]
  40. // Get all leafs (nodes without children).
  41. // Unlike `Int`, `IntX` panics if an error occurs.
  42. ints := client.Node.
  43. Query(). // All nodes.
  44. Where(node.Not(node.HasChildren())). // Only leafs.
  45. Order(ent.Asc(node.FieldValue)). // Order by their `value` field.
  46. GroupBy(node.FieldValue). // Extract only the `value` field.
  47. IntsX(ctx)
  48. fmt.Println(ints)
  49. // Output: [1 3 5]
  50. // Get orphan nodes (nodes without parent).
  51. // Unlike `Only`, `OnlyX` panics if an error occurs.
  52. orphan := client.Node.
  53. Query().
  54. Where(node.Not(node.HasParent())).
  55. OnlyX(ctx)
  56. fmt.Println(orphan)
  57. // Output: Node(id=1, value=2)
  58. return nil
  59. }

注意,外键列可以配置并暴露为实体字段,如下使用 Edge Field 选项:

  1. // Fields of the Node.
  2. func (Node) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.Int("parent_id").
  5. Optional(),
  6. }
  7. }
  8. // Edges of the Node.
  9. func (Node) Edges() []ent.Edge {
  10. return []ent.Edge{
  11. edge.To("children", Node.Type).
  12. From("parent").
  13. Unique().
  14. Field("parent_id"),
  15. }
  16. }

完整示例请参考 GitHub

多对多(M2M )两者之间

er-user-groups

在这个用户组和用户例子中,我们在用户组与其用户之间定义了一个多对多(M2M)的关系。 每个用户组能够拥有多个用户,并且每个用户可以加入到多个用户组中。

ent/schema/group.go

  1. // Edges of the Group.
  2. func (Group) Edges() []ent.Edge {
  3. return []ent.Edge{
  4. edge.To("users", User.Type),
  5. }
  6. }

ent/schema/user.go

  1. // Edges of the User.
  2. func (User) Edges() []ent.Edge {
  3. return []ent.Edge{
  4. edge.From("groups", Group.Type).
  5. Ref("users"),
  6. }
  7. }

与这些边的交互API如下:

  1. func Do(ctx context.Context, client *ent.Client) error {
  2. // Unlike `Save`, `SaveX` panics if an error occurs.
  3. hub := client.Group.
  4. Create().
  5. SetName("GitHub").
  6. SaveX(ctx)
  7. lab := client.Group.
  8. Create().
  9. SetName("GitLab").
  10. SaveX(ctx)
  11. a8m := client.User.
  12. Create().
  13. SetAge(30).
  14. SetName("a8m").
  15. AddGroups(hub, lab).
  16. SaveX(ctx)
  17. nati := client.User.
  18. Create().
  19. SetAge(28).
  20. SetName("nati").
  21. AddGroups(hub).
  22. SaveX(ctx)
  23. // Query the edges.
  24. groups, err := a8m.
  25. QueryGroups().
  26. All(ctx)
  27. if err != nil {
  28. return fmt.Errorf("querying a8m groups: %w", err)
  29. }
  30. fmt.Println(groups)
  31. // Output: [Group(id=1, name=GitHub) Group(id=2, name=GitLab)]
  32. groups, err = nati.
  33. QueryGroups().
  34. All(ctx)
  35. if err != nil {
  36. return fmt.Errorf("querying nati groups: %w", err)
  37. }
  38. fmt.Println(groups)
  39. // Output: [Group(id=1, name=GitHub)]
  40. // Traverse the graph.
  41. users, err := a8m.
  42. QueryGroups(). // [hub, lab]
  43. Where(group.Not(group.HasUsersWith(user.Name("nati")))). // [lab]
  44. QueryUsers(). // [a8m]
  45. QueryGroups(). // [hub, lab]
  46. QueryUsers(). // [a8m, nati]
  47. All(ctx)
  48. if err != nil {
  49. return fmt.Errorf("traversing the graph: %w", err)
  50. }
  51. fmt.Println(users)
  52. // Output: [User(id=1, age=30, name=a8m) User(id=2, age=28, name=nati)]
  53. return nil
  54. }

完整示例请参考 GitHub

多对多(M2M )自引用

er-following-followers

在以下的 粉丝-被关注者 例子中,用户与他们的粉丝是 多对多(M2M) 关系。 每个用户可以关注 多名 用户,也可以有 多名 粉丝。

ent/schema/user.go

  1. // Edges of the User.
  2. func (User) Edges() []ent.Edge {
  3. return []ent.Edge{
  4. edge.To("following", User.Type).
  5. From("followers"),
  6. }
  7. }

如你所见,在自引用的关系中,你可以把边和它的引用在同一个构造器中声明。

  1. func (User) Edges() []ent.Edge {
  2. return []ent.Edge{
  3. + edge.To("following", User.Type).
  4. + From("followers"),
  5. - edge.To("following", User.Type),
  6. - edge.From("followers", User.Type).
  7. - Ref("following"),
  8. }
  9. }

与这些边的交互API如下:

  1. func Do(ctx context.Context, client *ent.Client) error {
  2. // Unlike `Save`, `SaveX` panics if an error occurs.
  3. a8m := client.User.
  4. Create().
  5. SetAge(30).
  6. SetName("a8m").
  7. SaveX(ctx)
  8. nati := client.User.
  9. Create().
  10. SetAge(28).
  11. SetName("nati").
  12. AddFollowers(a8m).
  13. SaveX(ctx)
  14. // Query following/followers:
  15. flw := a8m.QueryFollowing().AllX(ctx)
  16. fmt.Println(flw)
  17. // Output: [User(id=2, age=28, name=nati)]
  18. flr := a8m.QueryFollowers().AllX(ctx)
  19. fmt.Println(flr)
  20. // Output: []
  21. flw = nati.QueryFollowing().AllX(ctx)
  22. fmt.Println(flw)
  23. // Output: []
  24. flr = nati.QueryFollowers().AllX(ctx)
  25. fmt.Println(flr)
  26. // Output: [User(id=1, age=30, name=a8m)]
  27. // Traverse the graph:
  28. ages := nati.
  29. QueryFollowers(). // [a8m]
  30. QueryFollowing(). // [nati]
  31. GroupBy(user.FieldAge). // [28]
  32. IntsX(ctx)
  33. fmt.Println(ages)
  34. // Output: [28]
  35. names := client.User.
  36. Query().
  37. Where(user.Not(user.HasFollowers())).
  38. GroupBy(user.FieldName).
  39. StringsX(ctx)
  40. fmt.Println(names)
  41. // Output: [a8m]
  42. return nil
  43. }

完整示例请参考 GitHub

多对多(M2M)双向自引用

er-user-friends

在这个用户与朋友的例子中,我们定义了一个 对称的多对多(M2M)关系 名为 friends。 每个用户可以 拥有多个 朋友。 如果用户 A 成为了 B 的朋友,那么 B 也是 A 的朋友。

注意在双向自引用场景下没有所有者/反向引用关系。

ent/schema/user.go

  1. // Edges of the User.
  2. func (User) Edges() []ent.Edge {
  3. return []ent.Edge{
  4. edge.To("friends", User.Type),
  5. }
  6. }

与这些边的交互API如下:

  1. func Do(ctx context.Context, client *ent.Client) error {
  2. // Unlike `Save`, `SaveX` panics if an error occurs.
  3. a8m := client.User.
  4. Create().
  5. SetAge(30).
  6. SetName("a8m").
  7. SaveX(ctx)
  8. nati := client.User.
  9. Create().
  10. SetAge(28).
  11. SetName("nati").
  12. AddFriends(a8m).
  13. SaveX(ctx)
  14. // Query friends. Unlike `All`, `AllX` panics if an error occurs.
  15. friends := nati.
  16. QueryFriends().
  17. AllX(ctx)
  18. fmt.Println(friends)
  19. // Output: [User(id=1, age=30, name=a8m)]
  20. friends = a8m.
  21. QueryFriends().
  22. AllX(ctx)
  23. fmt.Println(friends)
  24. // Output: [User(id=2, age=28, name=nati)]
  25. // Query the graph:
  26. friends = client.User.
  27. Query().
  28. Where(user.HasFriends()).
  29. AllX(ctx)
  30. fmt.Println(friends)
  31. // Output: [User(id=1, age=30, name=a8m) User(id=2, age=28, name=nati)]
  32. return nil
  33. }

完整示例请参考 GitHub

边字段

边的 Field 选项,允许用户将外键暴露为结构 (Schema) 上的常规字段。 注意,只有持有外键 (边 id) 的关系才能使用这个选项。 Support for non-foreign-key fields in join tables is in progress (as of September 2021) and can be tracked with this GitHub Issue.

  1. // Fields of the Post.
  2. func (Post) Fields() []ent.Field {
  3. return []ent.Field{
  4. field.Int("author_id").
  5. Optional(),
  6. }
  7. }
  8. // Edges of the Post.
  9. func (Post) Edges() []ent.Edge {
  10. return []ent.Edge{
  11. edge.To("author", User.Type).
  12. // Bind the "author_id" field to this edge.
  13. Field("author_id").
  14. Unique(),
  15. }
  16. }

与边字段的交互API如下:

  1. func Do(ctx context.Context, client *ent.Client) error {
  2. p, err := c.Post.Query().
  3. Where(post.AuthorID(id)).
  4. OnlyX(ctx)
  5. if err != nil {
  6. log.Fatal(err)
  7. }
  8. fmt.Println(p.AuthorID) // Access the "author" foreign-key.
  9. }

更多例子请参考 GitHub

Migration To Edge Fields

As mentioned in the StorageKey section, Ent configures edge storage-keys (e.g. foreign-keys) by the edge.To. Therefore, if you want to add a field to an existing edge (already exists in the database as a column), you need to set it up with the StorageKey option as follows:

  1. // Fields of the Post.
  2. func (Post) Fields() []ent.Field {
  3. return []ent.Field{
  4. + field.Int("author_id").
  5. + Optional(),
  6. }
  7. }
  8. // Edges of the Post.
  9. func (Post) Edges() []ent.Edge {
  10. return []ent.Edge{
  11. edge.From("author", User.Type).
  12. + Field("author_id").
  13. + StorageKey(edge.Column("post_author")).
  14. Unique(),
  15. }
  16. }

Alternatively, this option can be configured on the edge-field instead:

  1. // Fields of the Post.
  2. func (Post) Fields() []ent.Field {
  3. return []ent.Field{
  4. + field.Int("author_id").
  5. + StorageKey("post_author").
  6. + Optional(),
  7. }
  8. }

If you’re not sure how the foreign-key was named before using the edge-field option, check out the generated schema description in your project: <project>/ent/migrate/schema.go.

Required

Edges can be defined as required in the entity creation using the Required method on the builder.

  1. // Edges of the Card.
  2. func (Card) Edges() []ent.Edge {
  3. return []ent.Edge{
  4. edge.From("owner", User.Type).
  5. Ref("card").
  6. Unique().
  7. Required(),
  8. }
  9. }

If the example above, a card entity cannot be created without its owner.

Note that, starting with v0.10 , foreign key columns are created as NOT NULL in the database for required edges that are not self-reference . In order to migrate existing foreign key columns, use the Atlas Migration option. :::" class="reference-link">边 - 图11Note that, starting with v0.10, foreign key columns are created as NOT NULL in the database for required edges that are not self-reference. In order to migrate existing foreign key columns, use the Atlas Migration option. :::

存储字段

By default, Ent configures edge storage-keys by the edge-owner (the schema that holds the edge.To), and not the by back-reference (edge.From). This is because back-references are optional and can be removed.

In order to use custom storage configuration for edges, use the StorageKey method as follows:

  1. // Edges of the User.
  2. func (User) Edges() []ent.Edge {
  3. return []ent.Edge{
  4. edge.To("pets", Pet.Type).
  5. // Set the column name in the "pets" table for O2M relationship.
  6. StorageKey(edge.Column("owner_id")),
  7. edge.To("cars", Car.Type).
  8. // Set the symbol of the foreign-key constraint for O2M relationship.
  9. StorageKey(edge.Symbol("cars_owner_id")),
  10. edge.To("friends", User.Type).
  11. // Set the join-table, and the column names for a M2M relationship.
  12. StorageKey(edge.Table("friends"), edge.Columns("user_id", "friend_id")),
  13. edge.To("groups", Group.Type).
  14. // Set the join-table, its column names and the symbols
  15. // of the foreign-key constraints for M2M relationship.
  16. StorageKey(
  17. edge.Table("groups"),
  18. edge.Columns("user_id", "group_id"),
  19. edge.Symbols("groups_id1", "groups_id2")
  20. ),
  21. }
  22. }

结构体标记

Custom struct tags can be added to the generated entities using the StructTag method. Note that if this option was not provided, or provided and did not contain the json tag, the default json tag will be added with the field name.

  1. // Edges of the User.
  2. func (User) Edges() []ent.Edge {
  3. return []ent.Edge{
  4. edge.To("pets", Pet.Type).
  5. // Override the default json tag "pets" with "owner" for O2M relationship.
  6. StructTag(`json:"owner"`),
  7. }
  8. }

索引

Indexes can be defined on multi fields and some types of edges as well. However, you should note, that this is currently an SQL-only feature.

Read more about this in the Indexes section.

注解

Annotations is used to attach arbitrary metadata to the edge object in code generation. Template extensions can retrieve this metadata and use it inside their templates.

Note that the metadata object must be serializable to a JSON raw value (e.g. struct, map or slice).

  1. // Pet schema.
  2. type Pet struct {
  3. ent.Schema
  4. }
  5. // Edges of the Pet.
  6. func (Pet) Edges() []ent.Edge {
  7. return []ent.Field{
  8. edge.To("owner", User.Type).
  9. Ref("pets").
  10. Unique().
  11. Annotations(entgql.Annotation{
  12. OrderField: "OWNER",
  13. }),
  14. }
  15. }

Read more about annotations and their usage in templates in the template doc.

命名规范

By convention edge names should use snake_case. The corresponding struct fields generated by ent will follow the Go convention of using PascalCase. In cases where PascalCase is desired, you can do so with the StorageKey or StructTag methods.