登录
首页 >  Golang >  Go问答

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

来源:stackoverflow

时间:2024-02-13 12:06:24 296浏览 收藏

大家好,今天本人给大家带来文章《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删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>