概述

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

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 方法加以约束, 后续将作更多介绍。

2. 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.

存储字段

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. }

结构体标记

可以使用 StructTag 方法将自定义结构体标记添加到生成的实体中。 注意:如果此选项未设置,或者未包含 json 标记,那么默认的 json 标记将会设置为字段名。

  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.

更多信息请参考 索引(Indexes ) 章节。

注解

Annotations用于在生成代码时将任意的元数据附加到边对象。 模板扩展可以检索此元数据并直接使用。

注意元数据必须能序列化为JSON原始值(如struct,map或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. }

模板章节了解更多关于注解及其使用的相关信息。

命名规范

根据惯例字段的命名应使用snake_case(蛇形命名法)。 由ent生成相应的结构体字段使用是Go规范的PascalCase(帕斯卡命名法)。 在需要PascalCase 时,您可以借助StorageKeyStructTag方法实现。