2015-04-03 12 views
18

Sto usando Go con il GORM ORM. Ho le seguenti strutture. La relazione è semplice. Una città ha più Luoghi e una Piazza appartiene a una Città.Gorm Golang orm associations

type Place struct { 
    ID   int 
    Name  string 
    Town  Town 
} 

type Town struct { 
    ID int 
    Name string 
} 

Ora voglio interrogare tutti i luoghi e andare d'accordo con tutti i loro campi le informazioni della città corrispondente. Questo è il mio codice:

db, _ := gorm.Open("sqlite3", "./data.db") 
defer db.Close() 

places := []Place{} 
db.Find(&places) 
fmt.Println(places) 

mio database di esempio ha questi dati:

/* places table */ 
id name town_id 
1 Place1  1 
2 Place2  1 

/* towns Table */ 
id name 
1 Town1 
2 Town2 

sto ricevendo questo:

[{1 Place1 {0 }} {2 Mares Place2 {0 }}]

ma io sono in attesa di per ricevere qualcosa di simile (entrambi i posti bel ONG alla stessa città):

[{1 Place1 {1 Town1}} {2 Mares Place2 {1 Town1}}]

Come posso fare tale richiesta? Ho provato a usare Preloads e Related senza successo (probabilmente nel modo sbagliato). Non riesco a ottenere il risultato previsto.

+0

Cosa c'è nel database? Inoltre, hai provato la funzione 'Related'? – robbrit

+0

@robbrit Ho migliorato la domanda per riflettere i dati di esempio del database. No, ma ho provato ora la funzione 'Related' e ancora non riesco ad ottenere il risultato atteso. –

risposta

29

TownID deve essere specificato come chiave esterna. Il Place struct ottiene in questo modo:

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

Ora ci sono diverso approccio per gestire questa situazione. Per esempio:

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

Questo sarà certamente produrre il risultato previsto, a meno di notare l'output di registro e le query innescato.

[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}] 

L'uscita è il previsto ma questo approccio ha un difetto fondamentale, si noti che per ogni posto v'è la necessità di fare un'altra query db che producono una questione n + 1 problema. Questo potrebbe risolvere il problema ma diventerà rapidamente fuori controllo man mano che la quantità di posti crescerà.

Si scopre che l'approccio è abbastanza semplice utilizzando i precaricamenti.

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

Questo è tutto, il log delle query prodotta è:

[22.24ms] SELECT * FROM "places" 
[0.92ms] SELECT * FROM "towns" WHERE ("id" in ('1')) 

[{1 Place1 {1 Town1} 1} {2 Place2 {1 Town1} 1}] 

Questo approccio attiverà solo due interrogazioni, una per tutti i luoghi, e uno per tutte le città che ha luoghi. Questo approccio si adatta bene per quanto riguarda la quantità di luoghi e città (solo due query in tutti i casi).

+1

Mi sbaglio o Gorm non crea l'FK nel DB? – Alessio

+0

Alessio, l'hai risolto? –

2

Non si specifica la chiave esterna delle città nella propria struttura. Aggiungi semplicemente TownId alla tua struttura di Place e dovrebbe funzionare.

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) 
    } 
} 
+2

Grande. Funziona ora dopo aver letto questa risposta. Solo una nota/domanda aggiuntiva. Questo approccio sembra avere un problema con n + 1, poiché per ogni posto otteniamo una query aggiuntiva. È questo il comportamento corretto? –

+0

Se non vuoi specificare un join te stesso, temo che questo sia l'unico modo per usare Gorm, e sì, allora avrai un problema n + 1. Gorm supporta i join come thin wrapper su sql: https://github.com/jinzhu/gorm#joins, ma poi devi specificare la query. – olif

+1

Non ho alcun problema sull'implementazione di un join da solo, ma se lo faccio dovrò creare un'altra struttura solo per gestire il risultato della query. Voglio solo riutilizzare i due esistenti. La mia ipotesi è ok? –

3

Per ottimizzare domanda che uso "in condizione" nella stessa situazione

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] 
    } 
}