Я написал пример кода на основе вашего описания, вы можете найти полный код внизу. Позволяет go через различные опции:
// Use auto_preload and .First
db.Set("gorm:auto_preload", true).Where("name = ?", "m0").First(&modules)
Output:
[{ID:1 Name:m0 Status:0 Sections:[{ID:2 Name:s1 Fields:[]}]}]
Таким образом, с .First
вы получаете разделы, но не Sections.Fields
// Use auto_preload and .Find
db.Set("gorm:auto_preload", true).Find(&modules)
Output:
[{ID:1 Name:m0 Status:0 Sections:[{ID:2 Name:s1 Fields:[]}]} {ID:2 Name:m1 Status:0 Sections:[{ID:1 Name:s0 Fields:[]}]}]
С .Find
вы получаете то же самое поведение из .First
// Use .Preload
db.Preload("Sections").Preload("Sections.Fields").Find(&modules)
Output:
[{ID:1 Name:m0 Status:0 Sections:[{ID:2 Name:s1 Fields:[{ID:1 Name:f0} {ID:2 Name:f1}]}]} {ID:2 Name:m1 Status:0 Sections:[{ID:1 Name:s0 Fields:[{ID:1 Name:f0} {ID:3 Name:f2}]}]}]
При явной предварительной загрузке вы можете загрузить как Sections
, так и Sections.Fields
. Использование auto_preload
только предварительно загружает непосредственные поля, а не более глубокие поля. IMO, использующий явную предварительную загрузку, является общей практикой.
Полный код:
package main
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"log"
)
type Module struct {
ID int `gorm:"primary_key"`
Name string
Status int
Sections []Section `gorm:"many2many:module_sections;"`
}
type Section struct {
ID int `gorm:"primary_key"`
Name string
Fields []Field `gorm:"many2many:section_fields;"`
}
type Field struct {
ID int `gorm:"primary_key"`
Name string
}
func preloadingSample() error {
db, err := gorm.Open("sqlite3", "test.db")
if err != nil {
return fmt.Errorf("open DB failed: %w", err)
}
defer db.Close()
err = db.AutoMigrate(
&Module{},
&Section{},
&Field{},
).Error
if err != nil {
return fmt.Errorf("migration failed: %w", err)
}
// Delete everything in DB
db.Delete(&Module{})
db.Delete(&Section{})
db.Delete(&Field{})
// Put some sample data in DB
sampleFields := []Field{
{Name: "f0"},
{Name: "f1"},
{Name: "f2"},
{Name: "f3"},
}
for idx := range sampleFields {
err = db.Create(&sampleFields[idx]).Error
if err != nil {
return fmt.Errorf("failed to create: %w", err)
}
}
sampleSections := []Section{
{Name: "s0", Fields: []Field{sampleFields[0], sampleFields[2]}},
{Name: "s1", Fields: []Field{sampleFields[0], sampleFields[1]}},
}
for idx := range sampleSections {
err = db.Create(&sampleSections[idx]).Error
if err != nil {
return fmt.Errorf("failed to create: %w", err)
}
}
sampleModules := []Module{
{Name: "m0", Sections: []Section{sampleSections[1]}},
{Name: "m1", Sections: []Section{sampleSections[0]}},
}
for idx := range sampleModules {
err = db.Create(&sampleModules[idx]).Error
if err != nil {
return fmt.Errorf("failed to create: %w", err)
}
}
var modules []Module
// Load just one module with auto_preload
err = db.Set("gorm:auto_preload", true).Where("name = ?", "m0").First(&modules).Error
if err != nil {
return fmt.Errorf("auto_preload first: %w", err)
}
fmt.Printf("%+v\n", modules)
// Load all modules with auto_preload
err = db.Set("gorm:auto_preload", true).Find(&modules).Error
if err != nil {
return fmt.Errorf("auto_preload find: %w", err)
}
fmt.Printf("%+v\n", modules)
// Explicitly load
err = db.Preload("Sections").Preload("Sections.Fields").Find(&modules).Error
if err != nil {
return fmt.Errorf("preload find: %w", err)
}
fmt.Printf("%+v\n", modules)
return nil
}
func main() {
err := preloadingSample()
if err != nil {
log.Fatal(err)
}
}