Gorm Golang orm associations

后端 未结 5 730
死守一世寂寞
死守一世寂寞 2020-12-13 13:40

I\'m using Go with the GORM ORM. I have the following structs. The relation is simple. One Town has multiple Places and one Place belongs to one Town.

type P         


        
相关标签:
5条回答
  • 2020-12-13 14:04

    No need to loop for ids, just pluck the ids

    townIDs := []uint{}
    DB.Model(&Place{}).Pluck("town_id", &placeIDs)
    
    towns := []Town{}
    DB.Where(townIDs).Find(&towns)
    
    0 讨论(0)
  • 2020-12-13 14:16

    You do not specify the foreign key of towns in your Place struct. Simply add TownId to your Place struct and it should work.

    package main
    
    import (
        "fmt"
    
        "github.com/jinzhu/gorm"
        _ "github.com/mattn/go-sqlite3"
    )
    
    type Place struct {
        Id     int
        Name   string
        Town   Town
        TownId int //Foregin key
    }
    
    type Town struct {
        Id   int
        Name string
    }
    
    func main() {
        db, _ := gorm.Open("sqlite3", "./data.db")
        defer db.Close()
    
        db.CreateTable(&Place{})
        db.CreateTable(&Town{})
        t := Town{
            Name: "TestTown",
        }
    
        p1 := Place{
            Name:   "Test",
            TownId: 1,
        }
    
        p2 := Place{
            Name:   "Test2",
            TownId: 1,
        }
    
        err := db.Save(&t).Error
        err = db.Save(&p1).Error
        err = db.Save(&p2).Error
        if err != nil {
            panic(err)
        }
    
        places := []Place{}
        err = db.Find(&places).Error
        for i, _ := range places {
            db.Model(places[i]).Related(&places[i].Town)
        }
        if err != nil {
            panic(err)
        } else {
            fmt.Println(places)
        }
    }
    
    0 讨论(0)
  • 2020-12-13 14:24

    First change your model:

    type Place struct {
      ID          int
      Name        string
      Description string
      TownID      int
      Town        Town
    }
    

    And second, make preloading: https://gorm.io/docs/preload.html

    0 讨论(0)
  • 2020-12-13 14:30

    TownID must be specified as the foreign key. The Place struct gets like this:

    type Place struct {
      ID          int
      Name        string
      Description string
      TownID      int
      Town        Town
    }
    

    Now there are different approach to handle this. For example:

    places := []Place{}
    db.Find(&places)
    for i, _ := range places {
        db.Model(places[i]).Related(&places[i].Town)
    }
    

    This will certainly produce the expected result, but notice the log output and the queries triggered.

    [4.76ms]  SELECT  * FROM "places"
    [1.00ms]  SELECT  * FROM "towns"  WHERE ("id" = '1')
    [0.73ms]  SELECT  * FROM "towns"  WHERE ("id" = '1')
    
    [{1 Place1  {1 Town1} 1} {2 Place2  {1 Town1} 1}]
    

    The output is the expected but this approach has a fundamental flaw, notice that for every place there is the need to do another db query which produce a n + 1 problem issue. This could solve the problem but will quickly gets out of control as the amount of places grow.

    It turns out that the good approach is fairly simple using preloads.

    db.Preload("Town").Find(&places)
    

    That's it, the query log produced is:

    [22.24ms]  SELECT  * FROM "places"
    [0.92ms]  SELECT  * FROM "towns"  WHERE ("id" in ('1'))
    
    [{1 Place1  {1 Town1} 1} {2 Place2  {1 Town1} 1}]
    

    This approach will only trigger two queries, one for all places, and one for all towns that has places. This approach scales well regarding of the amount of places and towns (only two queries in all cases).

    0 讨论(0)
  • 2020-12-13 14:30

    To optimize query I use "in condition" in the same situation

    places := []Place{}
    
    DB.Find(&places)
    
    keys := []uint{}
    for _, value := range places {
        keys = append(keys, value.TownID)
    }
    
    rows := []Town{}
    DB.Where(keys).Find(&rows)
    
    related := map[uint]Town{}
    for _, value := range rows {
        related[value.ID] = value
    }
    
    for key, value := range places {
        if _, ok := related[value.TownID]; ok {
            res[key].Town = related[value.TownID]
        }
    }
    
    0 讨论(0)
提交回复
热议问题