当前位置:首页 > 文章列表 > Golang > Go问答 > Gorm - 根据需要预加载尽可能深的深度

Gorm - 根据需要预加载尽可能深的深度

来源:stackoverflow 2024-02-13 12:06:24 0浏览 收藏

大家好,今天本人给大家带来文章《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.idcommentidcomment.idtoplevelid 将包含最顶部的 comment id。如果 comment 是顶级 comment,则 toplevelid 将包含其自己的 id。这里的想法是每个 comment 都知道其顶级 commentid

// 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,
    }
}

创建评论 这里,更新前必须保存,才能知道commentid是设置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学习网公众号,一起学习编程~

版本声明
本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
go 中的枚举:Pythongo 中的枚举:Python
上一篇
go 中的枚举:Python
是否可以在结构中使用填充项,如空格?
下一篇
是否可以在结构中使用填充项,如空格?
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    542次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    508次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    497次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    484次学习
查看更多
AI推荐
  • SEO标题协启动:AI驱动的智能对话与内容生成平台 - 提升创作效率
    协启动
    SEO摘要协启动(XieQiDong Chatbot)是由深圳协启动传媒有限公司运营的AI智能服务平台,提供多模型支持的对话服务、文档处理和图像生成工具,旨在提升用户内容创作与信息处理效率。平台支持订阅制付费,适合个人及企业用户,满足日常聊天、文案生成、学习辅助等需求。
    2次使用
  • Brev AI:零注册门槛的全功能免费AI音乐创作平台
    Brev AI
    探索Brev AI,一个无需注册即可免费使用的AI音乐创作平台,提供多功能工具如音乐生成、去人声、歌词创作等,适用于内容创作、商业配乐和个人创作,满足您的音乐需求。
    2次使用
  • AI音乐实验室:一站式AI音乐创作平台,助力音乐创作
    AI音乐实验室
    AI音乐实验室(https://www.aimusiclab.cn/)是一款专注于AI音乐创作的平台,提供从作曲到分轨的全流程工具,降低音乐创作门槛。免费与付费结合,适用于音乐爱好者、独立音乐人及内容创作者,助力提升创作效率。
    2次使用
  • SEO标题PixPro:AI驱动网页端图像处理平台,提升效率的终极解决方案
    PixPro
    SEO摘要PixPro是一款专注于网页端AI图像处理的平台,提供高效、多功能的图像处理解决方案。通过AI擦除、扩图、抠图、裁切和压缩等功能,PixPro帮助开发者和企业实现“上传即处理”的智能化升级,适用于电商、社交媒体等高频图像处理场景。了解更多PixPro的核心功能和应用案例,提升您的图像处理效率。
    2次使用
  • EasyMusic.ai:零门槛AI音乐生成平台,专业级输出助力全场景创作
    EasyMusic
    EasyMusic.ai是一款面向全场景音乐创作需求的AI音乐生成平台,提供“零门槛创作 专业级输出”的服务。无论你是内容创作者、音乐人、游戏开发者还是教育工作者,都能通过EasyMusic.ai快速生成高品质音乐,满足短视频、游戏、广告、教育等多元需求。平台支持一键生成与深度定制,积累了超10万创作者,生成超100万首音乐作品,用户满意度达99%。
    3次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码