跳到主要内容

边(Edges)

快速摘要

边是实体的关系(或关联)。例如用户的宠物,或群组的用户:

在上面的示例中,你可以看到由边声明的两个关系。让我们一起看一下。

1. pets / owner 边; 用户(user)的宠物(pet)和宠物(pet)的主人(owner):

ent/schema/user.go
package schema

import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
)

// User schema.
type User struct {
ent.Schema
}

// Fields of the user.
func (User) Fields() []ent.Field {
return []ent.Field{
// ...
}
}

// Edges of the user.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type),
}
}

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

User 模式 拥有 pets/owner 关系,因为它使用 edge.ToPet 模式只是它的反向引用,使用 Ref 方法通过 edge.From 进行声明。

Ref 方法描述了我们想引用 User 模式中的哪条边,因为我们在两个模式中可以有多个引用。

边/关系的基数可以通过 Unique 方法进行控制,后面会展开解释。

2. users / groups 边; 组(group)的用户(user)和用户(user)的组(group):

ent/schema/group.go
package schema

import (
"entgo.io/ent"
"entgo.io/ent/schema/edge"
)

// Group schema.
type Group struct {
ent.Schema
}

// Fields of the group.
func (Group) Fields() []ent.Field {
return []ent.Field{
// ...
}
}

// Edges of the group.
func (Group) Edges() []ent.Edge {
return []ent.Edge{
edge.To("users", User.Type),
}
}

就像你看到的一样,一个组实体 可以有多个 用户,一个用户实体 可以有多个 组。 在关系定义中, users 边是一个 M2M(多对多)关系,groups边也是一个M2M(多对多)关系。

To和From

edge.Toedge.From 是创建边/关系的两个构建器。

模式用 edge.To 构建器定义它拥有关系的边,edge.From 只是给出了关系的反向引用(使用不同的名称)。

让我们看几个示例展示如何用边定义不同的关系类型。

关系

O2O两种类型一对一

在本示例中,一个用户 只有一张 信用卡,一张信用卡 只有一个 所有者。

User 模式定义了名为 cardedge.ToCard 模式通过 edge.From 定义了名为 owner 此边的反向引用。

ent/schema/user.go
// Edges of the user.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("card", Card.Type).
Unique(),
}
}

以下是与这些边进行交互的API:

func Do(ctx context.Context, client *ent.Client) error {
a8m, err := client.User.
Create().
SetAge(30).
SetName("Mashraki").
Save(ctx)
if err != nil {
return fmt.Errorf("creating user: %w", err)
}
log.Println("user:", a8m)
card1, err := client.Card.
Create().
SetOwner(a8m).
SetNumber("1020").
SetExpired(time.Now().Add(time.Minute)).
Save(ctx)
if err != nil {
return fmt.Errorf("creating card: %w", err)
}
log.Println("card:", card1)
// Only returns the card of the user,
// and expects that there's only one.
card2, err := a8m.QueryCard().Only(ctx)
if err != nil {
return fmt.Errorf("querying card: %w", err)
}
log.Println("card:", card2)
// The Card entity is able to query its owner using
// its back-reference.
owner, err := card2.QueryOwner().Only(ctx)
if err != nil {
return fmt.Errorf("querying owner: %w", err)
}
log.Println("owner:", owner)
return nil
}

完整示例参见 GitHub

O2O同种类型一对一

在这个链表示例中,我们定义了一个名为 next/prev递归关系。链表中的每个节点 只能有一个 next 指针指向下一个节点。若节点A通过 next 指针指向节点B,则节点B可通过 prev 指针(即反向引用边)获取指向A的指针。

ent/schema/node.go
// Edges of the Node.
func (Node) Edges() []ent.Edge {
return []ent.Edge{
edge.To("next", Node.Type).
Unique().
From("prev").
Unique(),
}
}

如您所见,对于同类型的关系,您可以在同一个构建器中声明边及其引用。

func (Node) Edges() []ent.Edge {
return []ent.Edge{
+ edge.To("next", Node.Type).
+ Unique().
+ From("prev").
+ Unique(),

- edge.To("next", Node.Type).
- Unique(),
- edge.From("prev", Node.Type).
- Ref("next").
- Unique(),
}
}

以下是与这些边进行交互的API:

func Do(ctx context.Context, client *ent.Client) error {
head, err := client.Node.
Create().
SetValue(1).
Save(ctx)
if err != nil {
return fmt.Errorf("creating the head: %w", err)
}
curr := head
// Generate the following linked-list: 1<->2<->3<->4<->5.
for i := 0; i < 4; i++ {
curr, err = client.Node.
Create().
SetValue(curr.Value + 1).
SetPrev(curr).
Save(ctx)
if err != nil {
return err
}
}

// Loop over the list and print it. `FirstX` panics if an error occur.
for curr = head; curr != nil; curr = curr.QueryNext().FirstX(ctx) {
fmt.Printf("%d ", curr.Value)
}
// Output: 1 2 3 4 5

// Make the linked-list circular:
// The tail of the list, has no "next".
tail, err := client.Node.
Query().
Where(node.Not(node.HasNext())).
Only(ctx)
if err != nil {
return fmt.Errorf("getting the tail of the list: %v", tail)
}
tail, err = tail.Update().SetNext(head).Save(ctx)
if err != nil {
return err
}
// Check that the change actually applied:
prev, err := head.QueryPrev().Only(ctx)
if err != nil {
return fmt.Errorf("getting head's prev: %w", err)
}
fmt.Printf("\n%v", prev.Value == tail.Value)
// Output: true
return nil
}

完整示例参见 GitHub

O2O一对一双向对等

在此用户-配偶示例中,我们定义了一个名为 spouse对称O2O一对一关系 。每个用户只能能有一个配偶。若用户A将其配偶(通过spouse)设为B,则B可通过spouse边获取其配偶信息。

请注意,在双向边的情况下,不存在所有者/逆向术语。

ent/schema/user.go
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("spouse", User.Type).
Unique(),
}
}

以下是与这些边进行交互的API:

func Do(ctx context.Context, client *ent.Client) error {
a8m, err := client.User.
Create().
SetAge(30).
SetName("a8m").
Save(ctx)
if err != nil {
return fmt.Errorf("creating user: %w", err)
}
nati, err := client.User.
Create().
SetAge(28).
SetName("nati").
SetSpouse(a8m).
Save(ctx)
if err != nil {
return fmt.Errorf("creating user: %w", err)
}

// Query the spouse edge.
// Unlike `Only`, `OnlyX` panics if an error occurs.
spouse := nati.QuerySpouse().OnlyX(ctx)
fmt.Println(spouse.Name)
// Output: a8m

spouse = a8m.QuerySpouse().OnlyX(ctx)
fmt.Println(spouse.Name)
// Output: nati

// Query how many users have a spouse.
// Unlike `Count`, `CountX` panics if an error occurs.
count := client.User.
Query().
Where(user.HasSpouse()).
CountX(ctx)
fmt.Println(count)
// Output: 2

// Get the user, that has a spouse with name="a8m".
spouse = client.User.
Query().
Where(user.HasSpouseWith(user.Name("a8m"))).
OnlyX(ctx)
fmt.Println(spouse.Name)
// Output: nati
return nil
}

请注意,外键列可通过以下 边字段 方式配置并作为实体字段暴露:

// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.Int("spouse_id").
Optional(),
}
}

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("spouse", User.Type).
Unique().
Field("spouse_id"),
}
}

完整示例参见 GitHub

O2M两种类型一对多

在这个用户-宠物示例中,用户与其宠物之间存在O2M关系。每个用户 有多个 宠物,而每只宠物 只有一个 主人。 若用户A通过 pets 边添加宠物B,则宠物B可通过 owner 边(即反向引用边)获取其主人信息。

请注意,从 Pet 模式的角度来看,该关系同样属于M2O(多对一)关系。

ent/schema/user.go
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type),
}
}

以下是与这些边进行交互的API:

func Do(ctx context.Context, client *ent.Client) error {
// Create the 2 pets.
pedro, err := client.Pet.
Create().
SetName("pedro").
Save(ctx)
if err != nil {
return fmt.Errorf("creating pet: %w", err)
}
lola, err := client.Pet.
Create().
SetName("lola").
Save(ctx)
if err != nil {
return fmt.Errorf("creating pet: %w", err)
}
// Create the user, and add its pets on the creation.
a8m, err := client.User.
Create().
SetAge(30).
SetName("a8m").
AddPets(pedro, lola).
Save(ctx)
if err != nil {
return fmt.Errorf("creating user: %w", err)
}
fmt.Println("User created:", a8m)
// Output: User(id=1, age=30, name=a8m)

// Query the owner. Unlike `Only`, `OnlyX` panics if an error occurs.
owner := pedro.QueryOwner().OnlyX(ctx)
fmt.Println(owner.Name)
// Output: a8m

// Traverse the sub-graph. Unlike `Count`, `CountX` panics if an error occurs.
count := pedro.
QueryOwner(). // a8m
QueryPets(). // pedro, lola
CountX(ctx) // count
fmt.Println(count)
// Output: 2
return nil
}

请注意,外键列可通过以下 边字段 方式配置并作为实体字段暴露:

ent/schema/pet.go
// Fields of the Pet.
func (Pet) Fields() []ent.Field {
return []ent.Field{
field.Int("owner_id").
Optional(),
}
}

// Edges of the Pet.
func (Pet) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type).
Ref("pets").
Unique().
Field("owner_id"),
}
}

完整示例参见 GitHub

O2M同种类型一对多

在此示例中,树节点与其子节点(或父节点)之间存在递归的O2M关系。 树中的每个节点 有多个 子节点,且 只有一个 父节点。若节点A将B添加为其子节点,则B可通过 owner 边获取其所属节点。

ent/schema/node.go
// Edges of the Node.
func (Node) Edges() []ent.Edge {
return []ent.Edge{
edge.To("children", Node.Type).
From("parent").
Unique(),
}
}

如你所见,对于同类型的关系,可以在同一个构建器中声明边及其引用。

func (Node) Edges() []ent.Edge {
return []ent.Edge{
+ edge.To("children", Node.Type).
+ From("parent").
+ Unique(),

- edge.To("children", Node.Type),
- edge.From("parent", Node.Type).
- Ref("children").
- Unique(),
}
}

以下是与这些边进行交互的API:

func Do(ctx context.Context, client *ent.Client) error {
root, err := client.Node.
Create().
SetValue(2).
Save(ctx)
if err != nil {
return fmt.Errorf("creating the root: %w", err)
}
// Add additional nodes to the tree:
//
// 2
// / \
// 1 4
// / \
// 3 5
//
// Unlike `Save`, `SaveX` panics if an error occurs.
n1 := client.Node.
Create().
SetValue(1).
SetParent(root).
SaveX(ctx)
n4 := client.Node.
Create().
SetValue(4).
SetParent(root).
SaveX(ctx)
n3 := client.Node.
Create().
SetValue(3).
SetParent(n4).
SaveX(ctx)
n5 := client.Node.
Create().
SetValue(5).
SetParent(n4).
SaveX(ctx)

fmt.Println("Tree leafs", []int{n1.Value, n3.Value, n5.Value})
// Output: Tree leafs [1 3 5]

// Get all leafs (nodes without children).
// Unlike `Int`, `IntX` panics if an error occurs.
ints := client.Node.
Query(). // All nodes.
Where(node.Not(node.HasChildren())). // Only leafs.
Order(ent.Asc(node.FieldValue)). // Order by their `value` field.
GroupBy(node.FieldValue). // Extract only the `value` field.
IntsX(ctx)
fmt.Println(ints)
// Output: [1 3 5]

// Get orphan nodes (nodes without parent).
// Unlike `Only`, `OnlyX` panics if an error occurs.
orphan := client.Node.
Query().
Where(node.Not(node.HasParent())).
OnlyX(ctx)
fmt.Println(orphan)
// Output: Node(id=1, value=2)

return nil
}

请注意,外键列可通过以下 边字段 方式配置并作为实体字段暴露:

// Fields of the Node.
func (Node) Fields() []ent.Field {
return []ent.Field{
field.Int("parent_id").
Optional(),
}
}

// Edges of the Node.
func (Node) Edges() []ent.Edge {
return []ent.Edge{
edge.To("children", Node.Type).
From("parent").
Unique().
Field("parent_id"),
}
}

完整示例参见 GitHub

M2M两种类型多对多

在此群组-用户示例中,群组与其用户之间存在多对多关系。 每个群组有多个用户,而每个用户可加入多个群组。

ent/schema/group.go
// Edges of the Group.
func (Group) Edges() []ent.Edge {
return []ent.Edge{
edge.To("users", User.Type),
}
}
ent/schema/user.go
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.From("groups", Group.Type).
Ref("users"),
}
}

以下是与这些边进行交互的API:

func Do(ctx context.Context, client *ent.Client) error {
// Unlike `Save`, `SaveX` panics if an error occurs.
hub := client.Group.
Create().
SetName("GitHub").
SaveX(ctx)
lab := client.Group.
Create().
SetName("GitLab").
SaveX(ctx)
a8m := client.User.
Create().
SetAge(30).
SetName("a8m").
AddGroups(hub, lab).
SaveX(ctx)
nati := client.User.
Create().
SetAge(28).
SetName("nati").
AddGroups(hub).
SaveX(ctx)

// Query the edges.
groups, err := a8m.
QueryGroups().
All(ctx)
if err != nil {
return fmt.Errorf("querying a8m groups: %w", err)
}
fmt.Println(groups)
// Output: [Group(id=1, name=GitHub) Group(id=2, name=GitLab)]

groups, err = nati.
QueryGroups().
All(ctx)
if err != nil {
return fmt.Errorf("querying nati groups: %w", err)
}
fmt.Println(groups)
// Output: [Group(id=1, name=GitHub)]

// Traverse the graph.
users, err := a8m.
QueryGroups(). // [hub, lab]
Where(group.Not(group.HasUsersWith(user.Name("nati")))). // [lab]
QueryUsers(). // [a8m]
QueryGroups(). // [hub, lab]
QueryUsers(). // [a8m, nati]
All(ctx)
if err != nil {
return fmt.Errorf("traversing the graph: %w", err)
}
fmt.Println(users)
// Output: [User(id=1, age=30, name=a8m) User(id=2, age=28, name=nati)]
return nil
}
备注

调用 AddGroups (一个 M2M 边)会导致无操作,因为边已经存在且它也不是边模式

a8m := client.User.
Create().
SetName("a8m").
AddGroups(
hub,
hub, // no-op.
).
SaveX(ctx)

完整示例参见 GitHub

M2M同种类型多对多

在这个关注-关注者的示例中,我们有一个关于用户和他们关注者的 M2M 的关系。每个用户可以关注 多个 用户,也可以有 多个 关注者。

ent/schema/user.go
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("following", User.Type).
From("followers"),
}
}

如你所见,对于同类型的关系,您可以在同一个构建器中声明边及其引用。

func (User) Edges() []ent.Edge {
return []ent.Edge{
+ edge.To("following", User.Type).
+ From("followers"),

- edge.To("following", User.Type),
- edge.From("followers", User.Type).
- Ref("following"),
}
}

以下是与这些边进行交互的API:

func Do(ctx context.Context, client *ent.Client) error {
// Unlike `Save`, `SaveX` panics if an error occurs.
a8m := client.User.
Create().
SetAge(30).
SetName("a8m").
SaveX(ctx)
nati := client.User.
Create().
SetAge(28).
SetName("nati").
AddFollowers(a8m).
SaveX(ctx)

// Query following/followers:

flw := a8m.QueryFollowing().AllX(ctx)
fmt.Println(flw)
// Output: [User(id=2, age=28, name=nati)]

flr := a8m.QueryFollowers().AllX(ctx)
fmt.Println(flr)
// Output: []

flw = nati.QueryFollowing().AllX(ctx)
fmt.Println(flw)
// Output: []

flr = nati.QueryFollowers().AllX(ctx)
fmt.Println(flr)
// Output: [User(id=1, age=30, name=a8m)]

// Traverse the graph:

ages := nati.
QueryFollowers(). // [a8m]
QueryFollowing(). // [nati]
GroupBy(user.FieldAge). // [28]
IntsX(ctx)
fmt.Println(ages)
// Output: [28]

names := client.User.
Query().
Where(user.Not(user.HasFollowers())).
GroupBy(user.FieldName).
StringsX(ctx)
fmt.Println(names)
// Output: [a8m]
return nil
}
备注

调用 AddFollowers (M2M 边) 不会产生任何操作,因为边已经存在且不是边模式

a8m := client.User.
Create().
SetName("a8m").
AddFollowers(
nati,
nati, // no-op.
).
SaveX(ctx)

完整示例参见 GitHub

M2M多对多双向对等

在这个用户-好友示例中,我们有一个名为 friends对等的 M2M 关系。 每个用户可以 有多个 好友。如果用户A是B的好友,那么B也是A的好友。

注意在这种双向对等边的情况下没有所有者/逆向术语。

ent/schema/user.go
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("friends", User.Type),
}
}

以下是与这些边进行交互的API:

func Do(ctx context.Context, client *ent.Client) error {
// Unlike `Save`, `SaveX` panics if an error occurs.
a8m := client.User.
Create().
SetAge(30).
SetName("a8m").
SaveX(ctx)
nati := client.User.
Create().
SetAge(28).
SetName("nati").
AddFriends(a8m).
SaveX(ctx)

// Query friends. Unlike `All`, `AllX` panics if an error occurs.
friends := nati.
QueryFriends().
AllX(ctx)
fmt.Println(friends)
// Output: [User(id=1, age=30, name=a8m)]

friends = a8m.
QueryFriends().
AllX(ctx)
fmt.Println(friends)
// Output: [User(id=2, age=28, name=nati)]

// Query the graph:
friends = client.User.
Query().
Where(user.HasFriends()).
AllX(ctx)
fmt.Println(friends)
// Output: [User(id=1, age=30, name=a8m) User(id=2, age=28, name=nati)]
return nil
}
备注

Calling AddFriends (a M2M bidirectional edge) will result in a no-op in case the edge already exists and is not an EdgeSchema:

a8m := client.User.
Create().
SetName("a8m").
AddFriends(
nati,
nati, // no-op.
).
SaveX(ctx)

完整示例参见 GitHub

边字段

边字段 Field 选项允许用户将外键作为常规字段暴露在模式中。请注意,仅允许持有外键(边ID)的关系使用此选项。

ent/schema/post.go
// Fields of the Post.
func (Post) Fields() []ent.Field {
return []ent.Field{
field.Int("author_id").
Optional(),
}
}

// Edges of the Post.
func (Post) Edges() []ent.Edge {
return []ent.Edge{
edge.To("author", User.Type).
// Bind the "author_id" field to this edge.
Field("author_id").
Unique(),
}
}

以下是与这些边进行交互的API:

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

参见 GitHub 中的多个示例。

迁移到边字段

就像在 存储名称 部分提到的, Ent 通过 edge.To 配置边存储名称(例如外键)。因此,若需向现有边(即数据库中已存在的列)添加字段,需通过 StorageKey 选项按以下方式配置:

// Fields of the Post.
func (Post) Fields() []ent.Field {
return []ent.Field{
+ field.Int("author_id").
+ Optional(),
}
}

// Edges of the Post.
func (Post) Edges() []ent.Edge {
return []ent.Edge{
edge.From("author", User.Type).
+ Field("author_id").
+ StorageKey(edge.Column("post_author")).
Unique(),
}
}

或者,也可以在边字段上配置此选项:

// Fields of the Post.
func (Post) Fields() []ent.Field {
return []ent.Field{
+ field.Int("author_id").
+ StorageKey("post_author").
+ Optional(),
}
}

若不确定在使用边字段选项前外键的命名方式,请查阅项目中生成的模式描述文件:<project>/ent/migrate/schema.go

边模式

边模式是M2M边的中间实体模式。通过 Through 选项用户可以为关系定义边模式。允许用户将关系暴露为公告API、存储额外的字段、使用CRUD并在边上设置狗子和隐私策略。

用户好友示例

在下面的示例中,我们将演示如何使用边模式来建模两个用户之间的友谊关系。该模式包含关系所需的两个必填字段(user_idfriend_id),以及一个名为 created_at 的附加字段,其值在创建时会自动设置。

ent/schema/user.go
// User holds the schema definition for the User entity.
type User struct {
ent.Schema
}

// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Default("Unknown"),
}
}

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("friends", User.Type).
Through("friendships", Friendship.Type),
}
}
信息
  • 与实体模式类似,若未另行说明,边模式的 ID 字段将自动生成。
  • 边模式不能被多个关系使用。
  • 在边模式中,user_idfriend_id 边字段是 必需 的,因为它们共同构成关系。

用户点赞示例

在本示例中,我们演示如何建模用户“点赞”推文并在数据库中存储“点赞”时间的系统。这种在边上存储额外字段的一种方式。

ent/schema/user.go
// User holds the schema definition for the User entity.
type User struct {
ent.Schema
}

// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Default("Unknown"),
}
}

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("liked_tweets", Tweet.Type).
Through("likes", Like.Type),
}
}
信息

在上面的示例中,field.ID 注解告诉 Ent 边模式的ID识别是由 user_idtweet_id 两个边字段组成的。因此,对于 Like 结构体而言任何构建方法(例如 GetOnlyID等)都不会生成 ID字段。

在其他边类型中使用边模式

在某些情况下,用户希望将O2M/M2O或O2O关系存储在独立表(即关联表)中,以便在边类型变更时简化后续迁移操作。例如,若需将O2M/M2O边转换为M2M边,可通过删除唯一约束实现,而非将外键值迁移至新表。

在下面的示例中,我们提出一种模型,允许用户"发布"推文,但受限于每条推文只能由单一用户撰写。 与常规的O2M/M2O边不同,通过采用边模式,我们利用 tweet_id 列上的唯一索引在关联表上强制执行此约束。该约束未来可能被移除,以允许多个用户参与推文的"创作"。因此,无需将数据迁移至新表即可将边类型更改为M2M。

ent/schema/user.go
// User holds the schema definition for the User entity.
type User struct {
ent.Schema
}

// Fields of the User.
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("name").
Default("Unknown"),
}
}

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("tweets", Tweet.Type).
Through("user_tweets", UserTweet.Type),
}
}

必填边

在构建器中使用 Required 方法可以将实体的边可以定义为必须的。

// Edges of the Card.
func (Card) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type).
Ref("card").
Unique().
Required(),
}
}

上面的示例中,卡实体若没有所有者则无法被创建。

信息

需要注意的是自 v0.10 开始, 数据库中必填边创建为 NOT NULL 的外键列不再是 自引用 。可以使用 Atlas 迁移 选项来迁移已存在的外键列。

不可变边(Immutable)

不可变边是指仅能在实体创建时设置或添加的边。即实体的更新构建器不会生成任何设置器。

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("tenant", Tenant.Type).
Field("tenant_id").
Unique().
Required().
Immutable(),
}
}

存储名称(StorageKey)

默认情况下,Ent 通过边拥有者(即存储 edge.To 的模式)配置边存储名称,而非通过反向引用(edge.From)。这是因为反向引用是可选的,且可能被移除。

使用 StorageKey 方法来为边进行自定义存储配置,如下:

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type).
// Set the column name in the "pets" table for O2M relationship.
StorageKey(edge.Column("owner_id")),
edge.To("cars", Car.Type).
// Set the symbol of the foreign-key constraint for O2M relationship.
StorageKey(edge.Symbol("cars_owner_id")),
edge.To("friends", User.Type).
// Set the join-table, and the column names for a M2M relationship.
StorageKey(edge.Table("friends"), edge.Columns("user_id", "friend_id")),
edge.To("groups", Group.Type).
// Set the join-table, its column names and the symbols
// of the foreign-key constraints for M2M relationship.
StorageKey(
edge.Table("groups"),
edge.Columns("user_id", "group_id"),
edge.Symbols("groups_id1", "groups_id2")
),
}
}

结构体标签(Struct Tags)

可通过 StructTag 方法为生成的实体添加自定义结构体标签。请注意,若未提供此选项,或提供但未包含 json 标签,则将默认添加包含字段名的 json 标签。

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

索引(Indexes)

索引可定义在多个字段以及某些类型的边上。但请注意,此功能目前仅限于SQL使用。

阅读 索引 部门获取更多信息.

评论(Comments)

可通过 .Comment() 方法在边上添加注解。该注解将在生成的实体代码中出现在边之前。支持使用 \n 转义序列实现换行。

// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type).
Comment("Pets that this user is responsible for taking care of.\n" +
"May be zero to many, depending on the user.")
}
}

注解(Annotations)

注解 Annotations 用于在代码生成过程中为边对象附加任意元数据。模板扩展可以检索这些元数据并在其模板内部使用。

请注意,元数据对象必须可序列化为 JSON 原始值(例如结构体、映射或切片)。

// Pet schema.
type Pet struct {
ent.Schema
}

// Edges of the Pet.
func (Pet) Edges() []ent.Edge {
return []ent.Edge{
edge.To("owner", User.Type).
Ref("pets").
Unique().
Annotations(entgql.RelayConnection()),
}
}

阅读 模板文档 了解更多注解及其在模板中的使用。

命名规范

按惯例,字段名称应采用蛇形命名法 snake_case。由 ent 生成的对应结构体字段将遵循 Go 语言惯例,采用驼峰命名法 PascalCase。若需使用驼峰命名法 PascalCase ,可通过 StorageKeyStructTag 方法实现。