open - Gorm Golang orm Asociaciones
join gorm (3)
No especifica la clave externa de las ciudades en la estructura de su Lugar. Simplemente agregue TownId a su estructura Place y debería funcionar.
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)
}
}
Estoy usando Go con el GORM ORM . Tengo las siguientes estructuras. La relación es simple. Una ciudad tiene varios lugares y un lugar pertenece a una ciudad.
type Place struct {
ID int
Name string
Town Town
}
type Town struct {
ID int
Name string
}
Ahora quiero consultar todos los lugares y llevarme bien con todos sus campos la información de la ciudad correspondiente. Este es mi código:
db, _ := gorm.Open("sqlite3", "./data.db")
defer db.Close()
places := []Place{}
db.Find(&places)
fmt.Println(places)
Mi base de datos de muestra tiene estos datos:
/* places table */
id name town_id
1 Place1 1
2 Place2 1
/* towns Table */
id name
1 Town1
2 Town2
estoy recibiendo esto:
[{1 Place1 {0 }} {2 Mares Place2 {0 }}]
Pero estoy esperando recibir algo como esto (ambos lugares pertenecen a la misma ciudad):
[{1 Place1 {1 Town1}} {2 Mares Place2 {1 Town1}}]
¿Cómo puedo hacer dicha consulta? Intenté usar Preloads
y Related
sin éxito (probablemente de forma incorrecta). No puedo ponerme a trabajar el resultado esperado.
Para optimizar la consulta utilizo "in condition" en la misma situación
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]
}
}
TownID
debe especificarse como la clave externa. La estructura del Place
pone así:
type Place struct {
ID int
Name string
Description string
TownID int
Town Town
}
Ahora hay diferentes enfoques para manejar esto. Por ejemplo:
places := []Place{}
db.Find(&places)
for i, _ := range places {
db.Model(places[i]).Related(&places[i].Town)
}
Esto ciertamente producirá el resultado esperado, pero observe la salida del registro y las consultas activadas.
[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}]
El resultado es el esperado, pero este enfoque tiene un defecto fundamental. Tenga en cuenta que para cada lugar existe la necesidad de realizar otra consulta de db que genere un problema de n + 1
. Esto podría resolver el problema pero se saldrá de control rápidamente a medida que la cantidad de lugares crezca.
Resulta que el buen enfoque es bastante simple usando precargas.
db.Preload("Town").Find(&places)
Eso es todo, el registro de consultas producido es:
[22.24ms] SELECT * FROM "places"
[0.92ms] SELECT * FROM "towns" WHERE ("id" in (''1''))
[{1 Place1 {1 Town1} 1} {2 Place2 {1 Town1} 1}]
Este enfoque solo activará dos consultas, una para todos los lugares y otra para todas las ciudades que tienen lugares. Este enfoque se adapta bien a la cantidad de lugares y ciudades (solo dos consultas en todos los casos).