Gorm - 根据需要预加载尽可能深的深度
大家好,今天本人给大家带来文章《Gorm - 根据需要预加载尽可能深的深度》,文中内容主要涉及到,如果你对Golang方面的知识点感兴趣,那就请各位朋友继续看下去吧~希望能真正帮到你们,谢谢!
我正在使用 gorm,并且对如何从模型中检索嵌套的 subcomments 有一些疑问。我遇到的问题是评论嵌套了两层,即 comment.subcomments 未加载。我是否缺少 preload 的某些内容?
我还认为我需要在 foreignkey:parent_id,parent_type 的 comment 上使用复合外键,但这不起作用。
https://goplay.tools/snippet/kohjus7x6nq
这是 asteriskdev 的另一次尝试:
https://goplay.tools/snippet/juu_w8b4cg-
您需要在本地运行代码,因为演示不支持 sqlite db。
package main
import (
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type blogpost struct {
id uint `gorm:"primary_key"`
content string
comments []comment `gorm:"foreignkey:parent_id;references:id"`
}
type parenttype int
const (
pt_blogpost parenttype = 1
pt_comment = 2
)
type comment struct {
id uint `gorm:"primary_key"`
parentid uint
parenttype parenttype
comment string
// todo composite foreign key not working
subcomments []comment `gorm:"foreignkey:parent_id,parent_type;references:id"`
}
func createcomment(parentid uint, parenttype parenttype) {
switch parenttype {
case pt_blogpost:
var blogpost blogpost
// lookup blog post
if err := models.db.where("id = ?", parentid).first(&blogpost).error; err != nil {
return
}
comment := comment{
parentid: parentid,
parenttype: pt_blogpost,
comment: "",
subcomments: nil,
}
models.db.create(&comment)
models.db.model(&blogpost).updates(&blogpost{
comments: append(blogpost.comments, comment),
})
case models.pt_comment:
var parentcomment comment
// lookup comment
if err := models.db.where("id = ?", parentid).first(&parentcomment).error; err != nil {
return
}
// create comment and add comment to db
comment := comment{
parentid: parentcomment.id,
parenttype: models.pt_comment,
comment: "",
subcomments: nil,
}
models.db.create(&comment)
// append to parent comment and persist parent comment
models.db.session(&gorm.session{fullsaveassociations: true}).model(&parentcomment).updates(&comment{
subcomments: append(parentcomment.subcomments, comment),
})
}
}
func getcommentsforblogpost(blogpostid uint) {
var comments []comment
// lookup comments by blogpostid
**// todo problem is it is not returning all nested comments**
**// i.e. the comments.subcomments**
if err := models.db.preload(clause.associations).
where(&comment{parenttype: pt_blogpost, parentid: blogpostid}).
find(&comments).error; err != nil {
return
}
}
尝试在 parentid 和 parenttype 上创建索引并将其设置为外键也不起作用:
type comment struct {
id uint `gorm:"primary_key"`
parentid uint `gorm:"index:idx_parent"`
parenttype parenttype `gorm:"index:idx_parent"`
comment string
// todo composite foreign key not working
subcomments []comment `gorm:"foreignkey:idx_parent"`
}
我在下面的注释行中收到错误:
in 为 struct comment 的字段找到有效字段 subcomments:为关系定义有效的外键或实现 valuer/scanner 接口
type CreateBlogPostInput struct {
Title string `json:"title" binding:"required"`
Content string `json:"content" binding:"required"`
}
func CreateBlogPost(input CreateBlogPostInput) {
var input CreateBlogPostInput
// Create blog post
blogPost := models.BlogPost{
Title: input.Title,
Content: input.Content,
Comments: []models.Comment{},
}
// ***Foreign key error here***
models.DB.Create(&blogPost)
}正确答案
编辑:
这就是我想出的方法来帮助您朝着正确的方向前进。现在我已经坐下来,我不知道预加载是否是您想要的。这取决于你需要进入多少层,无论它是否会开始变得麻烦。您不需要任何组合键,甚至不需要显式声明关系。这种编写方式,使用 gorm 命名约定,推断出关系。
使用纯 go sqlite 驱动程序并使用 gorm 记录器
import (
"errors"
"log"
"strings"
"time"
"github.com/glebarez/sqlite" // use the pure go sqlite driver
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/logger" // you need a logger for gorm
)
您的实现开启了 parenttype,所以我保留了它。
type parenttype int
const (
pt_blogpost parenttype = iota
pt_comment
)
您需要实时数据库连接。查看是否有则返回,没有则创建并返回。
var db *gorm.db
// db returns a live database connection
func db() *gorm.db {
var database *gorm.db
var err error
if db == nil {
database, err = gorm.open(sqlite.open("test.db"), &gorm.config{
logger: logger.default.logmode(logger.info), // you need a logger if you are going to do this.
nowfunc: func() time.time {
return time.now().local() // timestamps
},
})
if err != nil {
panic("failed to connect to database!")
}
db = database
}
return db
}
因为 blogpost 有一个 comments []comment 字段,并且 comment 有一个 blogpostid 字段,gorm 推断出关系。一个blogpost可以有多个comment 一个comment可以有多个subcomment。
// blogpost describes a blog post
type blogpost struct {
id uint `gorm:"primarykey"`
title string
content string
comments []comment
}
comment 可以有一个 blogpostid,该 id 引用与其关联的 blogpost。它还可以有一个commentid。 gorm 推断出这种关系。 gorm 将 blogpostid 读取为 blogpost.id。 commentid 为 comment.id。 toplevelid 将包含最顶部的 comment id。如果 comment 是顶级 comment,则 toplevelid 将包含其自己的 id。这里的想法是每个 comment 都知道其顶级 comment 的 id。
// associations are applied due to the naming convention eg. blogpostid refers to blogpost.id, commentid refers to comment.id
type comment struct {
id uint `gorm:"primarykey"`
comment string
blogpostid *uint // if this is attached to a blogpost, the blogpost id will be here otherwise nil
depth uint
toplevelcommentid uint
commentid *uint // if this is attached to a comment, the comment id will be here otherwise nil
subcomments []comment // subcomments will be here based on blogpostid or commentid if subcomments are preloaded
}
新 blogpost 的构造函数
// newblogpost instantiates and returns a *blogpost
func newblogpost(title, content string) *blogpost {
return &blogpost{
title: title,
content: content,
}
}
创建评论
这里,更新前必须保存,才能知道comment的id是设置toplevelcommentid。此函数将处理 comments 与 blogposts 或其他 comments 的关联,具体取决于它所附加到的父级。每个评论都知道它的 depth。每次创建新评论时,都会将深度最高的评论添加到顶层 comments depth depth 用于确定添加到 preload() gorm 方法中的 ".subcomment"s 的数量。
// new comment instantiates and returns a new comment associated with its parent
func newcomment(parentid uint, parenttype parenttype, comment string) (*comment, error) {
// create comment
c := comment{
blogpostid: &parentid,
comment: comment,
subcomments: nil,
}
switch parenttype {
case pt_blogpost:
blogpost := blogpost{}
// lookup blog post
if err := db().preload("comments").preload(clause.associations).where("id = ?", parentid).first(&blogpost).error; err != nil {
return nil, err
}
blogpost.comments = append(blogpost.comments, c)
db().session(&gorm.session{fullsaveassociations: true}).save(&c)
db().model(&c).updatecolumn("top_level_comment_id", c.id)
blogpost.comments = append(blogpost.comments, c)
return &c, nil
case pt_comment:
parentcomment := comment{}
// lookup comment
if err := db().preload("subcomments").preload(clause.associations).where("id = ?", parentid).first(&parentcomment).error; err != nil {
return nil, err
}
topcomment := comment{}
if err := db().where("id = ?", parentcomment.toplevelcommentid).first(&topcomment).error; err != nil {
return nil, err
}
topcomment.depth++
if err := db().model(&topcomment).updatecolumn("depth", topcomment.depth).first(&topcomment).error; err != nil {
return nil, err
}
// create comment and add comment to db
c.toplevelcommentid = parentcomment.toplevelcommentid
parentcomment.subcomments = append(parentcomment.subcomments, c)
return &parentcomment.subcomments[len(parentcomment.subcomments)-1], nil
}
return nil, errors.new("fell through parent type switch")
}
这些是完成保存到数据库的方法。
// create inserts the record in the db
func (bp *blogpost) create() (*blogpost, error) {
if err := db().create(&bp).error; err != nil {
return &blogpost{}, err
}
return bp, nil
}
// save saves a parent comment and all child associations to the database
func (c *comment) save() (*comment, error) {
if err := db().session(&gorm.session{fullsaveassociations: true}).save(c).error; err != nil {
return c, errors.new("error saving comment to db")
}
return c, nil
}
创建一个 blogpost 和 7 个嵌套的 comment。
递归地循环它们并打印出您找到的每个 comment。
func main() {
db().automigrate(&blogpost{})
db().automigrate(&comment{})
// blogpost
bp, err := newblogpost("blogposttitle", "blogpostcontent").create()
if err != nil {
log.println("error creating blogpost", err)
}
// top level comment under blogpost
c, err := newcomment(bp.id, pt_blogpost, "top level comment")
if err != nil {
log.println("error creating top level comment", err)
}
c, err = c.save()
if err != nil {
log.println("error creating top level comment", err)
}
c, err = newcomment(c.id, pt_comment, "second level comment")
if err != nil {
log.println("error creating second level comment", err)
}
c, err = c.save()
if err != nil {
log.println("error creating second level comment", err)
}
c, err = newcomment(c.id, pt_comment, "third level comment")
if err != nil {
log.println("error creating third level comment", err)
}
c, err = c.save()
if err != nil {
log.println("error creating third level comment", err)
}
c, err = newcomment(c.id, pt_comment, "fourth level comment")
if err != nil {
log.println("error creating fourth level comment", err)
}
c, err = c.save()
if err != nil {
log.println("error creating fourth level comment", err)
}
c, err = newcomment(c.id, pt_comment, "fifth level comment")
if err != nil {
log.println("error creating fifth level comment", err)
}
_, err = c.save()
if err != nil {
log.println("error creating fifth level comment", err)
}
c, err = newcomment(c.id, pt_comment, "sixth level comment")
if err != nil {
log.println("error creating sixth level comment", err)
}
_, err = c.save()
if err != nil {
log.println("error creating sixth level comment", err)
}
c, err = newcomment(c.id, pt_comment, "seventh level comment")
if err != nil {
log.println("error creating seventh level comment", err)
}
_, err = c.save()
if err != nil {
log.println("error creating seventh level comment", err)
}
for _, v := range bp.getcomments() {
commentid := uint(0)
blogpostid := uint(0)
if v.blogpostid != nil {
blogpostid = *v.blogpostid
}
if v.commentid != nil {
commentid = *v.commentid
}
log.printf("\n\nid: %d\ncomment: %s\ncommentid: %d\nblogpostid: %d\n", v.id, v.comment, commentid, blogpostid)
if v.subcomments != nil {
subcomments := v.subcomments
nextlevel:
for _, v := range subcomments {
commentid := uint(0)
blogpostid := uint(0)
if v.blogpostid != nil {
blogpostid = *v.blogpostid
}
if v.commentid != nil {
commentid = *v.commentid
}
log.printf("\n\nid: %d\ncomment: %s\ncommentid: %d\nblogpostid: %d\n", v.id, v.comment, commentid, blogpostid)
if v.subcomments != nil {
subcomments = v.subcomments
goto nextlevel // i use gotos to indicate recursion
}
}
}
}
}
这当然只是一个例子,希望有帮助。
完整源代码:
package main
import (
"errors"
"log"
"strings"
"time"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/logger"
)
type ParentType int
const (
PT_BlogPost ParentType = iota
PT_Comment
)
var dB *gorm.DB
// DB returns a live database connection
func DB() *gorm.DB {
var database *gorm.DB
var err error
if dB == nil {
database, err = gorm.Open(sqlite.Open("test.db"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // you need a logger if you are going to do this.
NowFunc: func() time.Time {
return time.Now().Local() // timestamps
},
})
if err != nil {
panic("Failed to connect to database!")
}
dB = database
}
return dB
}
// NewBlogPost instantiates and returns a *BlogPost
func NewBlogPost(title, content string) *BlogPost {
return &BlogPost{
Title: title,
Content: content,
}
}
// BlogPost describes a blog post
type BlogPost struct {
ID uint `gorm:"primaryKey"`
Title string
Content string
Comments []Comment
}
// Create inserts the record in the DB
func (bp *BlogPost) Create() (*BlogPost, error) {
if err := DB().Create(&bp).Error; err != nil {
return &BlogPost{}, err
}
return bp, nil
}
// GetComments retrieves comments and sub comments associated with a BlogPost
func (bp *BlogPost) GetComments() []Comment {
blogPost := BlogPost{}
sb := strings.Builder{}
sb.WriteString("Comments.SubComments")
err := DB().Preload("Comments").Find(&blogPost).Error
if err != gorm.ErrRecordNotFound {
Depth := uint(0)
for _, c := range blogPost.Comments {
if c.Depth > Depth {
Depth = c.Depth
}
}
i := uint(1)
for i < Depth {
sb.WriteString(".SubComments")
i++
}
}
DB().
Preload(sb.String()). // you may want to reconsider doing it with preloads.
Preload(clause.Associations). // also, you will accumulate tech debt as these structures get larger.
First(&blogPost) // .Error
return blogPost.Comments
}
// New comment instantiates and returns a new comment associated with its parent
func NewComment(parentId uint, parentType ParentType, comment string) (*Comment, error) {
// Create comment
c := Comment{
BlogPostID: &parentId,
Comment: comment,
SubComments: nil,
}
switch parentType {
case PT_BlogPost:
blogPost := BlogPost{}
// lookup blog post
if err := DB().Preload("Comments").Preload(clause.Associations).Where("id = ?", parentId).First(&blogPost).Error; err != nil {
return nil, err
}
blogPost.Comments = append(blogPost.Comments, c)
DB().Session(&gorm.Session{FullSaveAssociations: true}).Save(&c)
DB().Model(&c).UpdateColumn("top_level_comment_id", c.ID)
blogPost.Comments = append(blogPost.Comments, c)
return &c, nil
case PT_Comment:
parentComment := Comment{}
// lookup comment
if err := DB().Preload("SubComments").Preload(clause.Associations).Where("id = ?", parentId).First(&parentComment).Error; err != nil {
return nil, err
}
topComment := Comment{}
if err := DB().Where("id = ?", parentComment.TopLevelCommentID).First(&topComment).Error; err != nil {
return nil, err
}
topComment.Depth++
if err := DB().Model(&topComment).UpdateColumn("depth", topComment.Depth).First(&topComment).Error; err != nil {
return nil, err
}
// Create comment and add comment to db
c = Comment{
CommentID: &parentId,
Comment: comment,
SubComments: nil,
}
c.TopLevelCommentID = parentComment.TopLevelCommentID
parentComment.SubComments = append(parentComment.SubComments, c)
return &parentComment.SubComments[len(parentComment.SubComments)-1], nil
}
return nil, errors.New("fell through parent type switch")
}
// associations are applied due to the naming convention eg. BlogPostID refers to BlogPost.ID, CommentID refers to Comment.ID
type Comment struct {
ID uint `gorm:"primaryKey"`
Comment string
BlogPostID *uint // if this is attached to a blogpost, the blogpost ID will be here otherwise nil
Depth uint
TopLevelCommentID uint
CommentID *uint // if this is attached to a comment, the comment ID will be here otherwise nil
SubComments []Comment // SubComments will be here based on BlogPostID or CommentID if SubComments are preloaded
}
// Save saves a parent comment and all child associations to the database
func (c *Comment) Save() (*Comment, error) {
if err := DB().Session(&gorm.Session{FullSaveAssociations: true}).Save(c).Error; err != nil {
return c, errors.New("error saving comment to db")
}
return c, nil
}
func main() {
DB().AutoMigrate(&BlogPost{})
DB().AutoMigrate(&Comment{})
// blogpost
bp, err := NewBlogPost("BlogPostTitle", "BlogPostContent").Create()
if err != nil {
log.Println("error creating blogpost", err)
}
// top level comment under blogpost
c, err := NewComment(bp.ID, PT_BlogPost, "top level comment")
if err != nil {
log.Println("error creating top level comment", err)
}
c, err = c.Save()
if err != nil {
log.Println("error creating top level comment", err)
}
c, err = NewComment(c.ID, PT_Comment, "second level comment")
if err != nil {
log.Println("error creating second level comment", err)
}
c, err = c.Save()
if err != nil {
log.Println("error creating second level comment", err)
}
c, err = NewComment(c.ID, PT_Comment, "third level comment")
if err != nil {
log.Println("error creating third level comment", err)
}
c, err = c.Save()
if err != nil {
log.Println("error creating third level comment", err)
}
c, err = NewComment(c.ID, PT_Comment, "fourth level comment")
if err != nil {
log.Println("error creating fourth level comment", err)
}
c, err = c.Save()
if err != nil {
log.Println("error creating fourth level comment", err)
}
c, err = NewComment(c.ID, PT_Comment, "fifth level comment")
if err != nil {
log.Println("error creating fifth level comment", err)
}
_, err = c.Save()
if err != nil {
log.Println("error creating fifth level comment", err)
}
c, err = NewComment(c.ID, PT_Comment, "sixth level comment")
if err != nil {
log.Println("error creating sixth level comment", err)
}
_, err = c.Save()
if err != nil {
log.Println("error creating sixth level comment", err)
}
c, err = NewComment(c.ID, PT_Comment, "seventh level comment")
if err != nil {
log.Println("error creating seventh level comment", err)
}
_, err = c.Save()
if err != nil {
log.Println("error creating seventh level comment", err)
}
for _, v := range bp.GetComments() {
commentID := uint(0)
blogPostID := uint(0)
if v.BlogPostID != nil {
blogPostID = *v.BlogPostID
}
if v.CommentID != nil {
commentID = *v.CommentID
}
log.Printf("\n\nID: %d\nComment: %s\nCommentID: %d\nBlogpostID: %d\n", v.ID, v.Comment, commentID, blogPostID)
if v.SubComments != nil {
subComments := v.SubComments
nextLevel:
for _, v := range subComments {
commentID := uint(0)
blogPostID := uint(0)
if v.BlogPostID != nil {
blogPostID = *v.BlogPostID
}
if v.CommentID != nil {
commentID = *v.CommentID
}
log.Printf("\n\nID: %d\nComment: %s\nCommentID: %d\nBlogpostID: %d\n", v.ID, v.Comment, commentID, blogPostID)
if v.SubComments != nil {
subComments = v.SubComments
goto nextLevel // I use gotos to indicate recursion
}
}
}
}
}今天带大家了解了的相关知识,希望对你有所帮助;关于Golang的技术知识我们会一点点深入介绍,欢迎大家关注golang学习网公众号,一起学习编程~
go 中的枚举:Python
- 上一篇
- go 中的枚举:Python
- 下一篇
- 是否可以在结构中使用填充项,如空格?
-
- Golang · Go问答 | 1年前 |
- 在读取缓冲通道中的内容之前退出
- 139浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 戈兰岛的全球 GOPRIVATE 设置
- 204浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何将结构作为参数传递给 xml-rpc
- 325浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何用golang获得小数点以下两位长度?
- 478浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何通过 client-go 和 golang 检索 Kubernetes 指标
- 486浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 将多个“参数”映射到单个可变参数的习惯用法
- 439浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 将 HTTP 响应正文写入文件后出现 EOF 错误
- 357浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 结构中映射的匿名列表的“复合文字中缺少类型”
- 352浏览 收藏
-
- Golang · Go问答 | 1年前 |
- NATS Jetstream 的性能
- 101浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何将复杂的字符串输入转换为mapstring?
- 440浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 相当于GoLang中Java将Object作为方法参数传递
- 212浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何确保所有 goroutine 在没有 time.Sleep 的情况下终止?
- 143浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3176次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3388次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3417次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4522次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3796次使用
-
- GoLand调式动态执行代码
- 2023-01-13 502浏览
-
- 用Nginx反向代理部署go写的网站。
- 2023-01-17 502浏览
-
- Golang取得代码运行时间的问题
- 2023-02-24 501浏览
-
- 请问 go 代码如何实现在代码改动后不需要Ctrl+c,然后重新 go run *.go 文件?
- 2023-01-08 501浏览
-
- 如何从同一个 io.Reader 读取多次
- 2023-04-11 501浏览

