2015-04-03 3 views
18

Używam Go z GORM ORM. Mam następujące konstrukcje. Relacja jest prosta. Jedno miasto ma wiele Miejsc i jedno Miejsce należy do jednego Miasta.Gorm Golang orm association

type Place struct { 
    ID   int 
    Name  string 
    Town  Town 
} 

type Town struct { 
    ID int 
    Name string 
} 

Teraz chcę zapytać wszystkie miejsca i dogadać się ze wszystkimi polami z informacjami o odpowiednim mieście. To jest mój kod:

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

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

Moja przykładowa baza danych ma te dane:

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

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

Jestem odbierający to:

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

ale jestem spodziewa, aby otrzymać coś takiego (oba miejsca bel ong do tej samej miejscowości):

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

Jak mogę zrobić takie zapytanie? Próbowałem używać Preloads i Related bez powodzenia (prawdopodobnie w niewłaściwy sposób). Nie mogę uzyskać oczekiwanego rezultatu.

+0

Co znajduje się w bazie danych? Czy próbowałeś także funkcji 'Related'? – robbrit

+0

@robbrit Ulepszyłem pytanie, aby odzwierciedlić przykładowe dane z bazy danych. Nie, ale próbowałem teraz funkcji 'Related' i nadal nie udało mi się uzyskać oczekiwanego rezultatu. –

Odpowiedz

29

TownID musi być określony jako klucz obcy. Struktura Place wygląda następująco:

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

Istnieje teraz inne podejście do radzenia sobie z tym. Na przykład:

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

To na pewno spowoduje oczekiwany wynik, ale zauważ wynik logu i wywołane zapytania.

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

Wyjście jest oczekiwana, ale takie podejście ma podstawową wadę zauważyć, że za każdym miejscu, istnieje potrzeba, aby zrobić kolejny kwerendy db które produkują problem n + 1 problem. Może to rozwiązać problem, ale szybko wymknie się spod kontroli, ponieważ liczba miejsc rośnie.

Okazuje się, że podejście dobre jest dość proste przy użyciu preloads.

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

To jest to, dziennik zapytań produkowany jest:

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

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

Podejście to spowoduje tylko dwa pytania, jedno dla wszystkich miejsc i jeden dla wszystkich miast, które ma miejsca. Podejście to jest skalowane pod względem liczby miejsc i miast (tylko we wszystkich przypadkach są to dwa zapytania).

+1

Czy się mylę, czy Gorm nie tworzy FK w DB? – Alessio

+0

Alessio, rozwiązałeś to? –

2

Nie określasz obcego klucza miast w swojej strukturze Place. Po prostu dodaj TownId do swojej struktury Place i powinno działać.

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

Świetnie. Działa teraz po przeczytaniu tej odpowiedzi. Jeszcze jedna dodatkowa uwaga/pytanie. Takie podejście wydaje się mieć problem z n + 1, ponieważ dla każdego miejsca otrzymujemy dodatkowe zapytanie. Czy to właściwe zachowanie? –

+0

Jeśli nie chcesz samodzielnie określać sprzężenia, obawiam się, że jest to jedyny sposób korzystania z Gorm'a, i tak, wtedy będziesz miał problem z numerem n + 1. Gorm obsługuje złączenia jako cienkie opakowanie ponad sql: https://github.com/jinzhu/gorm#joins, ale musisz podać zapytanie. – olif

+1

Nie mam żadnego problemu dotyczącego implementacji join samodzielnie, ale jeśli to zrobię, będę musiał utworzyć kolejną strukturę tylko po to, aby obsłużyć wynik zapytania. Chcę tylko użyć dwóch istniejących. Czy moje założenie jest w porządku? –

3

celu optymalizacji kwerendy używam „stan” w tej samej sytuacji

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