Scott

数据库实体类常见8大关系 a year ago

go
database
14907个字符
共有154人围观

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
}