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
。此函数将处理 comment
s 与 blogpost
s 或其他 comment
s 的关联,具体取决于它所附加到的父级。每个评论都知道它的 depth
。每次创建新评论时,都会将深度最高的评论添加到顶层 comment
s 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

- 下一篇
- 是否可以在结构中使用填充项,如空格?
-
- Golang · Go问答 | 1年前 |
- 在读取缓冲通道中的内容之前退出
- 139浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 戈兰岛的全球 GOPRIVATE 设置
- 204浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何将结构作为参数传递给 xml-rpc
- 325浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何用golang获得小数点以下两位长度?
- 477浏览 收藏
-
- 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基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 协启动
- SEO摘要协启动(XieQiDong Chatbot)是由深圳协启动传媒有限公司运营的AI智能服务平台,提供多模型支持的对话服务、文档处理和图像生成工具,旨在提升用户内容创作与信息处理效率。平台支持订阅制付费,适合个人及企业用户,满足日常聊天、文案生成、学习辅助等需求。
- 2次使用
-
- Brev AI
- 探索Brev AI,一个无需注册即可免费使用的AI音乐创作平台,提供多功能工具如音乐生成、去人声、歌词创作等,适用于内容创作、商业配乐和个人创作,满足您的音乐需求。
- 2次使用
-
- AI音乐实验室
- AI音乐实验室(https://www.aimusiclab.cn/)是一款专注于AI音乐创作的平台,提供从作曲到分轨的全流程工具,降低音乐创作门槛。免费与付费结合,适用于音乐爱好者、独立音乐人及内容创作者,助力提升创作效率。
- 2次使用
-
- PixPro
- SEO摘要PixPro是一款专注于网页端AI图像处理的平台,提供高效、多功能的图像处理解决方案。通过AI擦除、扩图、抠图、裁切和压缩等功能,PixPro帮助开发者和企业实现“上传即处理”的智能化升级,适用于电商、社交媒体等高频图像处理场景。了解更多PixPro的核心功能和应用案例,提升您的图像处理效率。
- 2次使用
-
- EasyMusic
- EasyMusic.ai是一款面向全场景音乐创作需求的AI音乐生成平台,提供“零门槛创作 专业级输出”的服务。无论你是内容创作者、音乐人、游戏开发者还是教育工作者,都能通过EasyMusic.ai快速生成高品质音乐,满足短视频、游戏、广告、教育等多元需求。平台支持一键生成与深度定制,积累了超10万创作者,生成超100万首音乐作品,用户满意度达99%。
- 3次使用
-
- 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浏览