数据库实体类常见8大关系 a year ago
博客大纲
1, O2O 2 types
经典例子:用户和身份证
一个用户只有一个身份证,反之一个身份证也只对应一个用户
ent里的关系:
//ent/schema/user.go
// Edges of the user.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("card", Card.Type).
Unique(),
}
}
//ent/schema/card.go
// Edges of the Card.
func (Card) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type).
Ref("card").
Unique().
// We add the "Required" method to the builder
// to make this edge required on entity creation.
// i.e. Card cannot be created without its owner.
Required(),
}
}
edge间的交互:
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
}
2, O2O same type
经典例子:单链链表
在此例中,有一个递归关系叫做prev
,next
, 每个节点只有一个next
节点。 如果节点A使用next
指向B,那么B就可以反向的使用prev
拿到上一个节点
ent里的关系:
//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(),
}
}
需要注意的是:如果是实体类是same type,可以在同一个builder中定义edge和reference
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(),
}
}
edge间的交互:
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
}
3, O2O bidirectional(双向)
经典例子:用户配偶
在此例中,我们有一个名为“配偶”的对称 O2O 关系,每个人都只有一个配偶。 如果A把B设为ta的配偶,那么B可以通过spouse
这个edge拿到ta的配偶
ent里的关系:
//ent/schema/user.go
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("spouse", User.Type).
Unique(),
}
}
edge间的交互:
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
}
4, O2M 2 types
经典例子:用户和宠物
在此例中,我们有一个一对多的关系:一个用户可以有多个宠物,但是一个宠物只有一个user
如果用户A通过pets
这个edge添加了宠物B,那么B可以反向的通过使用owner
这条edge获取ta的主人
ent里的关系:
//ent/schema/user.go
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("pets", Pet.Type),
}
}
//ent/schema/pet.go
// Edges of the Pet.
func (Pet) Edges() []ent.Edge {
return []ent.Edge{
edge.From("owner", User.Type).
Ref("pets").
Unique(),
}
}
edge间的交互:
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
}
5, O2M same type
经典例子:node节点
在此示例中,我们在树的节点与其子节点(或其父节点)之间建立了递归 的”一对多” 关系。
树上的每个节点都有许多子节点和一个父节点。
如果节点 A通过children
edge 将 B 添加到其子节点,则 B 可以使用parent
edge获取其父节点。
ent里的关系:
//ent/schema/node.go
// Edges of the Node.
func (Node) Edges() []ent.Edge {
return []ent.Edge{
edge.To("children", Node.Type).
From("parent").
Unique(),
}
}
在关系类型相同的情况下,我们可以在同一个构建器builder中声明edge和back-reference。
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(),
}
}
edge间的交互:
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
}
6, M2M 2 types
经典例子:用户和群
在此示例中,群组和其用户之间存在 “多对多” 的关系。每个群组都有许多用户,每个用户可以加入许多群组。
ent里的关系:
//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"),
}
}
edge间的交互:
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
}
7, M2M same type
经典例子:关注和被关注
在此示例中,我们在用户的关注和被关注之间建立了 “多对多” 的关系。每个用户可以关注多个用户,也可以拥有多个被关注者。
ent里的关系:
//ent/schema/user.go
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("following", User.Type).
From("followers"),
}
}
在关系类型相同的情况下,我们可以在同一个构建器builder中声明edge和back-reference。
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"),
}
}
edge间的交互:
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
}
8, M2M bidirectional(双向)
经典例子:用户和朋友
在此示例中,我们有一个名为“friends”的对称 “多对多” 关系。每个用户可以有多个朋友。如果用户 A 成为 B 的朋友,则 B 也是 A 的朋友。
ent里的关系:
需要注意的是:双向无需加正反edge
//ent/schema/user.go
// Edges of the User.
func (User) Edges() []ent.Edge {
return []ent.Edge{
edge.To("friends", User.Type),
}
}
edge间的交互:
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
}