- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在使用 Gorm,对如何从模型中检索嵌套的 SubComments
有一些疑问。我遇到的问题是评论嵌套了两层深,即 Comment.SubComments
没有加载。我是否遗漏了 Preload
的内容?
我还认为我需要在 Comment of foreignKey:parent_id,parent_type
上使用复合外键,但这不起作用。
https://goplay.tools/snippet/kOhjUs7X6NQ
这是 asteriskdev 的另一个尝试:
https://goplay.tools/snippet/jUu_W8B4cg-
您需要在本地运行代码,因为 playground 不支持 sqlite 数据库。
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"`
}
我在下面的注释行中收到错误消息:为 struct Comment 的字段 SubComments 找到无效字段:为关系定义有效的外键或实现 Valuer/Scanner 接口(interface)
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,它指的是与其相关联的 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
与 BlogPost
或其他 Comment
的关联,具体取决于它附加到哪个父级。每个评论都知道它的深度
。每次创建新评论时,将具有最高深度的评论添加到顶层 Comment
的 Depth
Depth
用于确定添加到 Preload() GORM 方法的 ".SubComment"
的数量。
// 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
}
}
}
}
}
关于Gorm - 根据需要预加载深度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71983550/
我正在阅读哈德利的 Advanced R并尝试一些东西。我正在尝试创建一个 lazy闭包函数返回一个带有提供的函数 data.frame在其环境中以及使用 with并且能够在以后提供额外的函数参数。
我有两个 ViewController。初始 ViewController 是输入和存储 URL 的地方。此初始 ViewController 的 viewDidLoad 还应该在应用程序启动时开始加
你是怎么用的 对于应用程序中的 ListView 项也应该在设备 <11 上运行? 由于 activated_state 在 HC 之前不可用,我只能想到两个肮脏的解决方法: 在您的 Activit
我正在为 android (2.1 > 3.1) 编写一个应用程序,我想使用熟悉的做法,即在 Honeycomb 应用程序中使用应用程序图标来进入家庭 Activity ,但是,当我之前运行该 Act
如果搜索的键不存在,我如何覆盖方法 GET 或编写一个将在服务器端执行的新函数返回另一个键值? 示例: 如果关键字“word_1 word_2 word_3 word_4”不存在则搜索关键字“word
对于我的存储库,我使用的是 Git 和 Stash。在 Stash 端,我限制了(只读)对 master 的访问权限,因此任何用户都可以从 master 分支分支以获取功能/分支,但不能直接 merg
如何配置dgrid及其存储以定义渲染行时是否已经选择了行? 例如,如果我的行数据是这样的: { id: 1, name: 'Item Name', selected: true } 我当前
有没有一种方法可以将变量从一个 BeanShell 前/后处理器引用到另一个 BeanShell 处理器(它们在同一个线程组中)? 如果我在 HTTP 请求下的 BeanShell 预处理器中创建了一
问题 我已尝试添加预操作 shell 脚本,这些脚本会根据我正在构建的内容打开/关闭我的 .pch 文件中的某些定义。 但是,在运行构建时,没有任何反应。我不是一个流利的 shell 脚本编写者,所以
我有一个 HTML 字符串用作 jQuery 输入文档。 // the variable html contains the HTML code jQuery( html ).find( 'p' ).
在 Mercurial 中允许 merge 之前有没有办法进行一些检查? 通过将以下内容添加到 ~/.hg/hgrc,我找到了更新前 Hook ,并拥有一个在允许更新之前运行的脚本: [hooks]
总结: 预 Controller Hook 是否在缓存期间执行?是否有任何 Hook 点可以执行? (系统前?) 我应该强调一个事实,即 Hook 不会影响发送到浏览器的内容。这不是问题。 详细版:
我正在使用适用于 android 的 Skobbler Map API,到目前为止它一直非常好。按照官方的“操作方法”,我已经能够将 map 应用到我的应用程序中。比我可以让应用程序下载 map 并离
当我安装bcrypt时我的 hapi js 项目的模块尚未安装,它显示类似 node-pre-gyp install --fallback-to-build 我尝试通过运行来安装; npm i nod
我试图使用此代码的变体: apply plugin: 'java' apply plugin: 'idea' idea.workspace.iws.withXml { provider ->
假设我们有一个 PHP 项目,其依赖项 A 和 B 分别依赖于 PHP 库 X,但版本不同。 通常,人们会使用诸如 composer 之类的 PHP 依赖管理器,它可以通过在与 A 和 B 兼容的版本
这似乎违背了代码块的目的,但我希望能够在代码块中加粗。例如,如果我想将返回行加粗: int main(void) { **return 0;** } 最佳答案 您必须在 HTML 中执行此操作
我们是否应该使用 Huggingface(预)训练一个 BERT 无框模型的小写输入数据?我查看了 Thomas Wolf ( https://github.com/huggingface/trans
我有两个模式: 技能: var mongoose = require("mongoose"); var SkillSchema = new mongoose.Schema({ skill: {
我这里有问题。这适用于 Chrome,但我无法在 IE11 的 index.html 中使用任何动画。当它不想工作时,我会看到一个静态屏幕。同样在 IE 中,消息不会像它应该的那样消失。如果我将 di
我是一名优秀的程序员,十分优秀!