Golang gorm – 使用gorm预加载深层嵌套模型

huangapple go评论96阅读模式
英文:

Golang gorm - preload with deeply nested models

问题

我有一个人为的例子:

type Top struct {
  ID     uint `gorm:"primary_key"`
  Name   string
  Middle []*Middle
}

type Middle struct {
  ID    uint `gorm:"primary_key"`
  TopID int
  Name  string
  Low   []*Low
}

type Low struct {
  ID       uint `gorm:"primary_key"`
  MiddleID int
  Name     string
  Bottom   []*Bottom
}

type Bottom struct {
  ID    uint `gorm:"primary_key"`
  LowID int
  Name  string
}

top := Top{
  Name: "Top",
  Middle: []*Middle{
    {
      Name: "Middle",
      Low: []*Low{
        {
          Name: "Low",
          Bottom: []*Bottom{
            {
              Name: "Bottom",
            },
          },
        },
      },
    },
  },
}

if err := db.Save(&top).Error; err != nil {
  log.Fatal("保存计算时出错", err.Error())
}

if err := db.Where("id = 1").
  Preload("Middle.Low.Bottom").
  First(&top).Error; err != nil {
  log.Fatal(err)
}

这给出了以下输出:

(/host_home/workspace/golang/src/bitbucket.org/cloudgloballog/gormtest/main.go:81)
[2016-03-18 01:53:23]  [4.37ms]  INSERT INTO "tops" ("name") VALUES ('Top') RETURNING "tops"."id"

(/host_home/workspace/golang/src/bitbucket.org/cloudgloballog/gormtest/main.go:81)
[2016-03-18 01:53:23]  [3.49ms]  INSERT INTO "middles" ("name","top_id") VALUES ('Middle','1') RETURNING "middles"."id"

(/host_home/workspace/golang/src/bitbucket.org/cloudgloballog/gormtest/main.go:81)
[2016-03-18 01:53:23]  [1.07ms]  INSERT INTO "lows" ("middle_id","name") VALUES ('1','Low') RETURNING "lows"."id"

()
[2016-03-18 01:53:23]  [9.16ms]  INSERT INTO "bottoms" ("low_id","name") VALUES ('1','Bottom') RETURNING "bottoms"."id"

(/host_home/workspace/golang/src/bitbucket.org/cloudgloballog/gormtest/main.go:86)
[2016-03-18 01:53:23]  [1.54ms]  SELECT  * FROM "tops"   ORDER BY "tops"."id" ASC LIMIT 1

(/host_home/workspace/golang/src/bitbucket.org/cloudgloballog/gormtest/main.go:88)
[2016-03-18 01:53:23]  [2.63ms]  SELECT  * FROM "tops"  WHERE ("id" = '1') ORDER BY "tops"."id" ASC LIMIT 1

(/host_home/workspace/golang/src/bitbucket.org/cloudgloballog/gormtest/main.go:88)
[2016-03-18 01:53:23]  [1.65ms]  SELECT  * FROM "middles"  WHERE (top_id IN ('1')) ORDER BY "middles"."id" ASC

(/host_home/workspace/golang/src/bitbucket.org/cloudgloballog/gormtest/main.go:88)
[2016-03-18 01:53:23]  [4.20ms]  SELECT  * FROM "lows"  WHERE (middle_id IN ('1')) ORDER BY "lows"."id" ASC

(/host_home/workspace/golang/src/bitbucket.org/cloudgloballog/gormtest/main.go:88)
[2016-03-18 01:53:23]  找不到 []**main.Low 中的 Bottom 字段

如果我只嵌套 Top -> Middle -> Low 并调用 Preload("Middle.Low"),它可以正常工作。线索可能在于双指针引用 []**main.Low,但我无法弄清楚如何正确处理它。

可能不支持到这个层次的嵌套预加载。有人知道吗?

英文:

I have the following contrived example:

type Top struct {
  ID     uint `gorm:"primary_key"`
  Name   string
  Middle []*Middle
}

type Middle struct {
  ID    uint `gorm:"primary_key"`
  TopID int
  Name  string
  Low   []*Low
}

type Low struct {
  ID       uint `gorm:"primary_key"`
  MiddleID int
  Name     string
  Bottom   []*Bottom
}

type Bottom struct {
  ID    uint `gorm:"primary_key"`
  LowID int
  Name  string
}
top := Top{
  Name: "Top",
  Middle: []*Middle{
    {
      Name: "Middle",
      Low: []*Low{
        {
          Name: "Low",
          Bottom: []*Bottom{
            {
              Name: "Bottom",
            },
          },
        },
      },
    },
  },
}

if err := db.Save(&top).Error; err != nil {
  log.Fatal("Got errors when saving calc", err.Error())
}

if err := db.Where("id = 1").
  Preload("Middle.Low.Bottom").
  First(&top).Error; err != nil {
  log.Fatal(err)
}

This gives me the following output:

(/host_home/workspace/golang/src/bitbucket.org/cloudgloballog/gormtest/main.go:81)
[2016-03-18 01:53:23]  [4.37ms]  INSERT INTO "tops" ("name") VALUES ('Top') RETURNING "tops"."id"

(/host_home/workspace/golang/src/bitbucket.org/cloudgloballog/gormtest/main.go:81)
[2016-03-18 01:53:23]  [3.49ms]  INSERT INTO "middles" ("name","top_id") VALUES ('Middle','1') RETURNING "middles"."id"

(/host_home/workspace/golang/src/bitbucket.org/cloudgloballog/gormtest/main.go:81)
[2016-03-18 01:53:23]  [1.07ms]  INSERT INTO "lows" ("middle_id","name") VALUES ('1','Low') RETURNING "lows"."id"

()
[2016-03-18 01:53:23]  [9.16ms]  INSERT INTO "bottoms" ("low_id","name") VALUES ('1','Bottom') RETURNING "bottoms"."id"

(/host_home/workspace/golang/src/bitbucket.org/cloudgloballog/gormtest/main.go:86)
[2016-03-18 01:53:23]  [1.54ms]  SELECT  * FROM "tops"   ORDER BY "tops"."id" ASC LIMIT 1

(/host_home/workspace/golang/src/bitbucket.org/cloudgloballog/gormtest/main.go:88)
[2016-03-18 01:53:23]  [2.63ms]  SELECT  * FROM "tops"  WHERE ("id" = '1') ORDER BY "tops"."id" ASC LIMIT 1

(/host_home/workspace/golang/src/bitbucket.org/cloudgloballog/gormtest/main.go:88)
[2016-03-18 01:53:23]  [1.65ms]  SELECT  * FROM "middles"  WHERE (top_id IN ('1')) ORDER BY "middles"."id" ASC

(/host_home/workspace/golang/src/bitbucket.org/cloudgloballog/gormtest/main.go:88)
[2016-03-18 01:53:23]  [4.20ms]  SELECT  * FROM "lows"  WHERE (middle_id IN ('1')) ORDER BY "lows"."id" ASC

(/host_home/workspace/golang/src/bitbucket.org/cloudgloballog/gormtest/main.go:88)
[2016-03-18 01:53:23]  can't find field Bottom in []**main.Low

If I were to only nest Top -> Middle -> Low and called Preload("Middle.Low") it works fine. The clue is probably in the double pointer reference []**main.Low but I'm unable to work out how to do this properly.

It's possible that nested preloading is not supported to this level. Anyone know?

答案1

得分: 2

这个问题似乎在早期版本的gorm中存在,如下所述:Issue1Issue2Issue3,但最新的预加载问题修复在这个链接中。

看起来这个问题已经解决了一段时间,但具体修复的版本没有记录,除非有人搜索git提交日期。

使用最新版本(截至本文撰写时的v1.9.11)设置一个当前示例没有问题。

package main

import (
	"log"

	"github.com/davecgh/go-spew/spew"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/sqlite"
)

type Top struct {
	ID     uint   `gorm:"primary_key"`
	Name   string
	Middle []*Middle
}

type Middle struct {
	ID    uint   `gorm:"primary_key"`
	TopID int
	Name  string
	Low   []*Low
}

type Low struct {
	ID       uint   `gorm:"primary_key"`
	MiddleID int
	Name     string
	Bottom   []*Bottom
}

type Bottom struct {
	ID    uint   `gorm:"primary_key"`
	LowID int
	Name  string
}

func main() {
	db, err := gorm.Open("sqlite3", "test.db")
	if err != nil {
		panic("failed to connect database")
	}
	defer db.Close()
	// 启用日志记录,显示详细日志
	db.LogMode(true)
	// 迁移模式
	db.AutoMigrate(&Top{})
	db.AutoMigrate(&Middle{})
	db.AutoMigrate(&Low{})
	db.AutoMigrate(&Bottom{})

	top := Top{
		Name: "Top",
		Middle: []*Middle{
			{
				Name: "Middle",
				Low: []*Low{
					{
						Name: "Low",
						Bottom: []*Bottom{
							{
								Name: "Bottom",
							},
						},
					},
				},
			},
		},
	}

	if err := db.Save(&top).Error; err != nil {
		log.Fatal("保存计算时出错", err.Error())
	}
	var ntop Top
	if err := db.Where("id = 1").
		Preload("Middle.Low.Bottom").
		First(&ntop).Error; err != nil {
		log.Fatal(err)
	}

	spew.Dump(&ntop)
}
$ ./main.exe

(C:/Users/fakename/source/stackoverflow/go/gorm-nested/main.go:70)
[2019-10-31 14:44:23]  [2.00ms]  INSERT  INTO "tops" ("name") VALUES ('Top')
[1 rows affected or returned ]

(C:/Users/fakename/source/stackoverflow/go/gorm-nested/main.go:70)
[2019-10-31 14:44:23]  [0.00ms]  INSERT  INTO "middles" ("top_id","name") VALUES (1,'Middle')
[1 rows affected or returned ]

(C:/Users/fakename/source/stackoverflow/go/gorm-nested/main.go:70)
[2019-10-31 14:44:23]  [0.99ms]  INSERT  INTO "lows" ("middle_id","name") VALUES (1,'Low')
[1 rows affected or returned ]

()
[2019-10-31 14:44:23]  [0.00ms]  INSERT  INTO "bottoms" ("low_id","name") VALUES (1,'Bottom')
[1 rows affected or returned ]

(C:/Users/fakename/source/stackoverflow/go/gorm-nested/main.go:76)
[2019-10-31 14:44:23]  [0.00ms]  SELECT * FROM "tops"  WHERE (id = 1) ORDER BY "tops"."id" ASC LIMIT 1
[1 rows affected or returned ]

(C:/Users/fakename/source/stackoverflow/go/gorm-nested/main.go:76)
[2019-10-31 14:44:23]  [0.00ms]  SELECT * FROM "middles"  WHERE ("top_id" IN (1)) ORDER BY "middles"."id" ASC
[1 rows affected or returned ]

(C:/Users/fakename/source/stackoverflow/go/gorm-nested/main.go:76)
[2019-10-31 14:44:23]  [0.00ms]  SELECT * FROM "lows"  WHERE ("middle_id" IN (1)) ORDER BY "lows"."id" ASC
[1 rows affected or returned ]

(C:/Users/fakename/source/stackoverflow/go/gorm-nested/main.go:76)
[2019-10-31 14:44:23]  [0.99ms]  SELECT * FROM "bottoms"  WHERE ("low_id" IN (1)) ORDER BY "bottoms"."id" ASC
[1 rows affected or returned ]
(*main.Top)(0xc00015f950)({
 ID: (uint) 1,
 Name: (string) (len=3) "Top",
 Middle: ([]*main.Middle) (len=1 cap=1) {
  (*main.Middle)(0xc000150fc0)({
   ID: (uint) 1,
   TopID: (int) 1,
   Name: (string) (len=6) "Middle",
   Low: ([]*main.Low) (len=1 cap=1) {
    (*main.Low)(0xc000151080)({
     ID: (uint) 1,
     MiddleID: (int) 1,
     Name: (string) (len=3) "Low",
     Bottom: ([]*main.Bottom) (len=1 cap=1) {
      (*main.Bottom)(0xc0001929c0)({
       ID: (uint) 1,
       LowID: (int) 1,
       Name: (string) (len=6) "Bottom"
      })
     }
    })
   }
  })
 }
})
英文:

This issue seems to have been present in earlier versions of gorm as described in: Issue1, Issue2, Issue3, but the lastest preload issue fix has been in this one.

It appears it has been resolved for quite some time but the exact version it was fixed is not documented unless someone searches git commit dates.

Setting up a current example with the latest version (v1.9.11 as of this writing) held no issues.

package main

import (
	"log"

	"github.com/davecgh/go-spew/spew"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/sqlite"
)

type Top struct {
	ID     uint `gorm:"primary_key"`
	Name   string
	Middle []*Middle
}

type Middle struct {
	ID    uint `gorm:"primary_key"`
	TopID int
	Name  string
	Low   []*Low
}

type Low struct {
	ID       uint `gorm:"primary_key"`
	MiddleID int
	Name     string
	Bottom   []*Bottom
}

type Bottom struct {
	ID    uint `gorm:"primary_key"`
	LowID int
	Name  string
}

func main() {
	db, err := gorm.Open("sqlite3", "test.db")
	if err != nil {
		panic("failed to connect database")
	}
	defer db.Close()
	// Enable Logger, show detailed log
	db.LogMode(true)
	// Migrate the schema
	db.AutoMigrate(&Top{})
	db.AutoMigrate(&Middle{})
	db.AutoMigrate(&Low{})
	db.AutoMigrate(&Bottom{})

	top := Top{
		Name: "Top",
		Middle: []*Middle{
			{
				Name: "Middle",
				Low: []*Low{
					{
						Name: "Low",
						Bottom: []*Bottom{
							{
								Name: "Bottom",
							},
						},
					},
				},
			},
		},
	}

	if err := db.Save(&top).Error; err != nil {
		log.Fatal("Got errors when saving calc", err.Error())
	}
	var ntop Top
	if err := db.Where("id = 1").
		Preload("Middle.Low.Bottom").
		First(&ntop).Error; err != nil {
		log.Fatal(err)
	}

	spew.Dump(&ntop)
}

$ ./main.exe

(C:/Users/fakename/source/stackoverflow/go/gorm-nested/main.go:70)
[2019-10-31 14:44:23]  [2.00ms]  INSERT  INTO "tops" ("name") VALUES ('Top')
[1 rows affected or returned ]

(C:/Users/fakename/source/stackoverflow/go/gorm-nested/main.go:70)
[2019-10-31 14:44:23]  [0.00ms]  INSERT  INTO "middles" ("top_id","name") VALUES (1,'Middle')
[1 rows affected or returned ]

(C:/Users/fakename/source/stackoverflow/go/gorm-nested/main.go:70)
[2019-10-31 14:44:23]  [0.99ms]  INSERT  INTO "lows" ("middle_id","name") VALUES (1,'Low')
[1 rows affected or returned ]

()
[2019-10-31 14:44:23]  [0.00ms]  INSERT  INTO "bottoms" ("low_id","name") VALUES (1,'Bottom')
[1 rows affected or returned ]

(C:/Users/fakename/source/stackoverflow/go/gorm-nested/main.go:76)
[2019-10-31 14:44:23]  [0.00ms]  SELECT * FROM "tops"  WHERE (id = 1) ORDER BY "tops"."id" ASC LIMIT 1
[1 rows affected or returned ]

(C:/Users/fakename/source/stackoverflow/go/gorm-nested/main.go:76)
[2019-10-31 14:44:23]  [0.00ms]  SELECT * FROM "middles"  WHERE ("top_id" IN (1)) ORDER BY "middles"."id" ASC
[1 rows affected or returned ]

(C:/Users/fakename/source/stackoverflow/go/gorm-nested/main.go:76)
[2019-10-31 14:44:23]  [0.00ms]  SELECT * FROM "lows"  WHERE ("middle_id" IN (1)) ORDER BY "lows"."id" ASC
[1 rows affected or returned ]

(C:/Users/fakename/source/stackoverflow/go/gorm-nested/main.go:76)
[2019-10-31 14:44:23]  [0.99ms]  SELECT * FROM "bottoms"  WHERE ("low_id" IN (1)) ORDER BY "bottoms"."id" ASC
[1 rows affected or returned ]
(*main.Top)(0xc00015f950)({
 ID: (uint) 1,
 Name: (string) (len=3) "Top",
 Middle: ([]*main.Middle) (len=1 cap=1) {
  (*main.Middle)(0xc000150fc0)({
   ID: (uint) 1,
   TopID: (int) 1,
   Name: (string) (len=6) "Middle",
   Low: ([]*main.Low) (len=1 cap=1) {
    (*main.Low)(0xc000151080)({
     ID: (uint) 1,
     MiddleID: (int) 1,
     Name: (string) (len=3) "Low",
     Bottom: ([]*main.Bottom) (len=1 cap=1) {
      (*main.Bottom)(0xc0001929c0)({
       ID: (uint) 1,
       LowID: (int) 1,
       Name: (string) (len=6) "Bottom"
      })
     }
    })
   }
  })
 }
})

huangapple
  • 本文由 发表于 2016年3月18日 09:57:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/36074811.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定