web-dev-qa-db-fra.com

Associations d'ormes de Gorm Golang

J'utilise Go avec le GORM ORM . J'ai les structures suivantes. La relation est simple. Une ville a plusieurs lieux et un lieu 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 endroits et obtenir avec tous leurs champs les informations de la ville correspondante. Voici 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 ces données:

/* 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 ça (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 n'arrive pas à obtenir le résultat escompté.

28
Javier Cadiz

TownID doit être spécifié comme clé étrangère. La structure Place se présente comme suit:

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

Maintenant, il existe différentes approches 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 n + 1 problème de problème. Cela pourrait résoudre le problème, mais deviendra rapidement incontrôlable à mesure que le nombre de places augmente.

Il s'avère que l'approche bonne est assez simple en utilisant des précharges.

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

Voilà, le journal des requêtes 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 fonction du nombre de lieux et de villes (seulement deux requêtes dans tous les cas).

46
Javier Cadiz

Pour optimiser la requête, j'utilise "en 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]
    }
}
5
Max

Vous ne spécifiez pas la clé étrangère des villes dans votre structure Place. Ajoutez simplement TownId à votre structure 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)
    }
}
5
olif