通过Has Many关系的父级更新时出现重复条目

huangapple go评论81阅读模式
英文:

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

根据你的问题,我理解你想要实现以下两个功能:

  1. 替换特定quizquestions
  2. 通过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:

  1. Replace the questions of a specific quiz
  2. Being able to edit a specific question by its id

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!

huangapple
  • 本文由 发表于 2023年3月27日 04:46:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/75850470.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定