英文:
Duplicate entry when updating through parent of Has Many relationship
问题
Quiz和Question之间存在一个"有多个"的关系。
type Quiz struct {
ID uint `json:"id"`
UserID string `json:"userId"`
Author string `json:"author"`
Name string `json:"name"`
Description string `json:"description"`
//与Question存在有多个关系
Questions []Question `json:"questions" gorm:"constraint:OnDelete:CASCADE;"`
}
type Question struct {
ID uint `json:"id"`
Question string `json:"question"`
//属于Quiz
QuizID uint
}
我想看看如果我发送一个PUT请求,将一个测验中的问题替换为其他问题,就像我使用MongoDB而不是MySQL一样,会发生什么。
要更新的测验
{
"id": 7,
"userId": "642097c0f85c7e2a7c170460",
"author": "User",
"name": "Quiz 2",
"description": "Description 2",
"questions": [
{
"id": 5,
"question": "How are you?",
"QuizID": 7
}
]
}
PUT api/quiz/7,使用空数组
{
"name": "Quiz 2",
"description": "Description 2",
"questions": []
}
代码
func UpdateQuiz(c *fiber.Ctx) error {
// 解析id
id, err := c.ParamsInt("id")
if err != nil {
return c.Status(http.StatusBadRequest).JSON("请确保:id是一个整数")
}
// 查找测验
var quiz models.Quiz
if err := findPreloadedQuiz(id, &quiz); err != nil {
return c.Status(http.StatusBadRequest).JSON(err.Error())
}
fmt.Println(1, quiz)
// 检查作者
if ok := isAuthor(c, &quiz); !ok {
return nil
}
//TODO 如何更新问题
if err := c.BodyParser(&quiz); err != nil {
return c.Status(http.StatusInternalServerError).JSON(err.Error())
}
fmt.Println(2, quiz)
// 保存
database.Instance.Save(&quiz)
return c.Status(http.StatusOK).JSON(quiz)
}
日志
[quiz-pod] 1 {7 642097c0f85c7e2a7c170460 User Quiz 2 Description 2 [{5 How are you? 7}]}
[quiz-pod] 2 {7 642097c0f85c7e2a7c170460 User Quiz 2 Description 2 []}
[quiz-pod]
[quiz-pod] 2023/03/26 20:04:25 /quiz/routes/quiz.go:123 Error 1062 (23000): Duplicate entry '7' for key 'PRIMARY'
[quiz-pod] [5.176ms] [rows:0] INSERT INTO `quizzes` (`user_id`,`author`,`name`,`description`,`id`) VALUES ('642097c0f85c7e2a7c170460','User','Quiz 2','Description 2',7)
我不明白为什么会出现错误Error 1062 (23000): Duplicate entry '7' for key 'PRIMARY'
;gorm是不是尝试创建一个新的测验?(如果我发送一个不带questions
字段的请求,名称和描述确实会被更新)
当尝试更新特定问题时,出现相同的错误。
{
"name": "Quiz 2",
"description": "Description 2",
"questions": [
{
"id": 5,
"question": "Why are you?",
"QuizID": 7
}
]
}
[quiz-pod] 1 {7 642097c0f85c7e2a7c170460 User Quiz 2 Description 2 [{5 How are you? 7}]}
[quiz-pod] 2 {7 642097c0f85c7e2a7c170460 User Quiz 2 Description 2 [{5 Why are you? 7}]}
[quiz-pod]
[quiz-pod] 2023/03/26 20:07:55 /quiz/routes/quiz.go:123 Error 1062 (23000): Duplicate entry '7' for key 'PRIMARY'
[quiz-pod] [1.970ms] [rows:0] INSERT INTO `quizzes` (`user_id`,`author`,`name`,`description`,`id`) VALUES ('642097c0f85c7e2a7c170460','User','Quiz 2','Description 2',7)
如果错误是Duplicate entry '5' for key 'PRIMARY'
,那么可以解释为具有ID为5的问题已经存在。
(使用database.Instance.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&quiz)
可以工作,但我仍然想了解错误的原因以及为什么需要显式启用关联模式。为了能够删除问题,我使用了database.Instance.Model(&quiz).Association("Questions").Replace(&quiz.Questions)
)
英文:
There is a "has many" relationship between Quiz and Question.
type Quiz struct {
ID uint `json:"id"`
UserID string `json:"userId"`
Author string `json:"author"`
Name string `json:"name"`
Description string `json:"description"`
//has many relationship with Question
Questions []Question `json:"questions" gorm:"constraint:OnDelete:CASCADE;"`
}
type Question struct {
ID uint `json:"id"`
Question string `json:"question"`
//belongs to Quiz
QuizID uint
}
I wanted to see what would happen if I sent a PUT request to replace the questions in a quiz with other questions as if I was using MongoDB and not MySQL.
quiz to update
{
"id": 7,
"userId": "642097c0f85c7e2a7c170460",
"author": "User",
"name": "Quiz 2",
"description": "Description 2",
"questions": [
{
"id": 5,
"question": "How are you?",
"QuizID": 7
}
]
}
PUT api/quiz/7 with empty array
{
"name": "Quiz 2",
"description": "Description 2",
"questions": [
]
}
code
func UpdateQuiz(c *fiber.Ctx) error {
// parse id
id, err := c.ParamsInt("id")
if err != nil {
return c.Status(http.StatusBadRequest).JSON("Please make sure that :id is an integer")
}
// find quiz
var quiz models.Quiz
if err := findPreloadedQuiz(id, &quiz); err != nil {
return c.Status(http.StatusBadRequest).JSON(err.Error())
}
fmt.Println(1, quiz)
// check if author
if ok := isAuthor(c, &quiz); !ok {
return nil
}
//TODO how to update question
if err := c.BodyParser(&quiz); err != nil {
return c.Status(http.StatusInternalServerError).JSON(err.Error())
}
fmt.Println(2, quiz)
// save
database.Instance.Save(&quiz)
return c.Status(http.StatusOK).JSON(quiz)
}
logs
[quiz-pod] 1 {7 642097c0f85c7e2a7c170460 User Quiz 2 Description 2 [{5 How are you? 7}]}
[quiz-pod] 2 {7 642097c0f85c7e2a7c170460 User Quiz 2 Description 2 []}
[quiz-pod]
[quiz-pod] 2023/03/26 20:04:25 /quiz/routes/quiz.go:123 Error 1062 (23000): Duplicate entry '7' for key 'PRIMARY'
[quiz-pod] [5.176ms] [rows:0] INSERT INTO `quizzes` (`user_id`,`author`,`name`,`description`,`id`) VALUES ('642097c0f85c7e2a7c170460','User','Quiz 2','Description 2',7)
I don't understand why the error Error 1062 (23000): Duplicate entry '7' for key 'PRIMARY'
happens; is gorm trying to create a new quiz? (If I send a request without the questions
field, the name and description do get updated)
The same error happens when trying to update a specific question.
{
"name": "Quiz 2",
"description": "Description 2",
"questions": [
{
"id": 5,
"question": "Why are you?",
"QuizID": 7
}
]
}
[quiz-pod] 1 {7 642097c0f85c7e2a7c170460 User Quiz 2 Description 2 [{5 How are you? 7}]}
[quiz-pod] 2 {7 642097c0f85c7e2a7c170460 User Quiz 2 Description 2 [{5 Why are you? 7}]}
[quiz-pod]
[quiz-pod] 2023/03/26 20:07:55 /quiz/routes/quiz.go:123 Error 1062 (23000): Duplicate entry '7' for key 'PRIMARY'
[quiz-pod] [1.970ms] [rows:0] INSERT INTO `quizzes` (`user_id`,`author`,`name`,`description`,`id`) VALUES ('642097c0f85c7e2a7c170460','User','Quiz 2','Description 2',7)
If the error was Duplicate entry '5' for key 'PRIMARY'
instead, it could be interpreted as the question with id 5 already exists.
(Using database.Instance.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&quiz)
does work, but I still want to understand the error and why associations mode needs to be explicitly enabled. To be able to delete questions as well, I use database.Instance.Model(&quiz).Association("Questions").Replace(&quiz.Questions)
)
答案1
得分: 1
根据你的问题,我理解你想要实现以下两个功能:
- 替换特定
quiz
的questions
- 通过
id
编辑特定的question
如果我理解正确,下面的代码应该可以实现这些功能。
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=root -p 3306:3306 -d mysql:latest
type Quiz struct {
ID uint `json:"id"`
Author string `json:"author"`
Questions []Question `json:"questions" gorm:"constraint:OnDelete:CASCADE;"`
}
type Question struct {
ID uint `json:"id"`
Question string `json:"question"`
QuizID uint
}
func main() {
dsn := "root:root@tcp(127.0.0.1:3306)/questions?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&Quiz{})
db.AutoMigrate(&Question{})
// 种子数据
questionToIns := []Question{
{ID: 1, Question: "问题1"},
{ID: 2, Question: "问题2"},
}
quizToIns := []Quiz{
{ID: 1, Author: "John Doe", Questions: questionToIns},
}
db.Create(quizToIns)
// 开始替换
if err = db.Where("quiz_id = ?", 1).Delete(&Question{}).Error; err != nil {
panic(err)
}
questionToUpd := []Question{
{ID: 3, Question: "新问题", QuizID: 1},
}
if err = db.Create(questionToUpd).Error; err != nil {
panic(err)
}
// 结束替换
// 开始编辑
var question Question
if err = db.Find(&question, 3).Error; err != nil {
panic(err)
}
question.Question = "更新的描述"
if err = db.Updates(question).Error; err != nil {
panic(err)
}
// 结束编辑
}
让我给你分享一些注意事项。
首先,在文件的顶部,你可以看到我使用Docker在我的机器上运行MySQL实例的命令。
然后,你可以看到一个用于在表中填充示例数据的代码。请注意,为了演示的简洁性,我对你的结构进行了一些简化。
之后,你可以看到处理第一个需求(替换questions)的代码。这段代码被包裹在// 开始替换
和// 结束替换
的注释之间。
最后,我添加了处理编辑需求的代码,它被包裹在// 开始编辑
和// 结束编辑
的注释之间。
就是这样!你应该能够满足你的需求,如果有问题,请告诉我!
英文:
from what I got from your question, you'd like to achieve these two things:
- Replace the
questions
of a specificquiz
- Being able to edit a specific
question
by itsid
If I understood well, the following code should manage all of them.
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=root -p 3306:3306 -d mysql:latest
type Quiz struct {
ID uint `json:"id"`
// UserID string `json:"userId"`
Author string `json:"author"`
// Name string `json:"name"`
// Description string `json:"description"`
// has many relationship with Question
Questions []Question `json:"questions" gorm:"constraint:OnDelete:CASCADE;"`
}
type Question struct {
ID uint `json:"id"`
Question string `json:"question"`
// belongs to Quiz
QuizID uint
}
func main() {
dsn := "root:root@tcp(127.0.0.1:3306)/questions?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&Quiz{})
db.AutoMigrate(&Question{})
// seed data
questionToIns := []Question{
{ID: 1, Question: "question 1"},
{ID: 2, Question: "question 2"},
}
quizToIns := []Quiz{
{ID: 1, Author: "John Doe", Questions: questionToIns},
}
db.Create(quizToIns)
// start replacing
if err = db.Where("quiz_id = ?", 1).Delete(&Question{}).Error; err != nil {
panic(err)
}
questionToUpd := []Question{
{ID: 3, Question: "new question", QuizID: 1},
}
if err = db.Create(questionToUpd).Error; err != nil {
panic(err)
}
// end replacing
// start editing
var question Question
if err = db.Find(&question, 3).Error; err != nil {
panic(err)
}
question.Question = "updated description"
if err = db.Updates(question).Error; err != nil {
panic(err)
}
// end editing
}
Let me share with you a couple of notes.
First, at the top of the file, you can see the command used to run a MySQL instance on my machine through Docker.
Then, you can see a code to seed sample data in the tables. Please be aware that I simplified a little your structs just for the sake of the demo.
Afterward, you can see the code dealing with the first requirement (questions replacement). This code is wrapped within the comment // start replacing
and // end replacing
.
Lastly, I added the code dealing with the editing requirement that is wrapped within the comments // start editing
and // end editing
.
That's it! You should be able to manage your requirements too, let me know!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论