Gorm Golang orm associations
J'utilise Go avec le Gorm ORM . J'ai le texte suivant des structures. La relation est simple. Une ville a plusieurs endroits et un endroit appartient à une ville.
type Place struct {
ID int
Name string
Town Town
}
type Town struct {
ID int
Name string
}
Maintenant, je veux interroger tous les lieux et m'entendre avec tous leurs champs les informations de la ville correspondante. C'est mon code:
db, _ := gorm.Open("sqlite3", "./data.db")
defer db.Close()
places := []Place{}
db.Find(&places)
fmt.Println(places)
Mon exemple de base de données contient les données suivantes:
/* places table */
id name town_id
1 Place1 1
2 Place2 1
/* towns Table */
id name
1 Town1
2 Town2
Je reçois ceci:
[{1 Place1 {0 }} {2 Mares Place2 {0 }}]
Mais je m'attends à ce que reçoive quelque chose comme ceci (les deux endroits appartiennent à la même ville):
[{1 Place1 {1 Town1}} {2 Mares Place2 {1 Town1}}]
Comment puis-je faire une telle requête ? J'ai essayé d'utiliser Preloads
et Related
sans succès (probablement dans le mauvais sens). Je ne peux pas obtenir le résultat attendu.
3 réponses
TownID
doit être spécifié comme clé étrangère. La structure Place
obtient comme ceci:
type Place struct {
ID int
Name string
Description string
TownID int
Town Town
}
Maintenant, il existe une approche différente pour gérer cela. Par exemple:
places := []Place{}
db.Find(&places)
for i, _ := range places {
db.Model(places[i]).Related(&places[i].Town)
}
Cela produira certainement le résultat attendu, mais notez la sortie du journal et les requêtes déclenchées.
[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}]
La sortie est attendue mais cette approche a un défaut fondamental, notez que pour chaque endroit, il est nécessaire de faire une autre requête db qui produit un problème n + 1
. Cela pourrait résoudre le problème, mais va rapidement devient hors de contrôle que la quantité de places se développent.
Il s'avère que l'approche good est assez simple en utilisant des préchargements.
db.Preload("Town").Find(&places)
Voilà, le journal de requête produit est:
[22.24ms] SELECT * FROM "places"
[0.92ms] SELECT * FROM "towns" WHERE ("id" in ('1'))
[{1 Place1 {1 Town1} 1} {2 Place2 {1 Town1} 1}]
Cette approche ne déclenchera que deux requêtes, une pour tous les lieux, et une pour toutes les villes qui ont des lieux. Cette approche évolue bien en ce qui concerne le nombre de lieux et de villes (seulement deux requêtes dans tous les cas).
Vous ne spécifiez pas la clé étrangère des villes dans votre structure de Place. Ajoutez simplement TownId à votre structure de Place et cela devrait fonctionner.
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)
}
}
Pour optimiser la requête, j'utilise "in condition" dans la même 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]
}
}