Gorm 多对多关系重复值
Gorm many-to-many relationship duplicates values
我正在尝试在我的演示 API 中,在 Job
和技能列表 []Skill
之间建立多对多关系。
工作结构
type Job struct {
ID string `sql:"type:uuid;primary_key;"`
Title string `json:"title,omitempty"`
Skills []*skill.Skill `json:"skills,omitempty"gorm:"many2many:job_skill;"`
CreatedAt time.Time
UpdatedAt time.Time
}
技能结构
type Skill struct {
gorm.Model
Name string `json:"name,omitempty"`
}
然后我使用 gorm.DB.AutoMigrate()
自动生成连接 table。
当我向我的 API 发送 POST
请求时,数据在第一时间正确创建,并且连接 table 如您所料填充。
示例POST
数据
{
"title": "Senior Python Engineer",
"skills": [{"name": "javascript"}, {"name": "python"}]
}
但是当我发送 PATCH
添加新技能的请求时,它会在技能 table 中复制该技能,然后在加入 table 中创建一条新记录已经存在的技能。
示例PATCH
数据
{
"title": "Lead Engineer",
"skills": [{"name": "javascript"}, {"name": "python"}, {"name": "management"}]
}
对数据进行获取请求将显示以下内容:
{
"title": "Lead Engineer",
"skills": [{"name": "javascript"}, {"name": "python"}, {"name": "javascript"}, {"name": "python"}, {"name": "management"}]
}
我也试过设置 gorm:"unique"
击中的技能 Name
但是当添加新技能时它失败了,因为它说其他两个已经存在,这很好但是不会添加新的。
我假设我只能发回新值?不是整个列表?
为清楚起见,我的一些 Go 代码
func GetJobs(w http.ResponseWriter, r *http.Request) {
j := &[]Job{}
o := database.DB.Preload("Skills").Find(&j)
render.JSON(w, r, o)
}
func UpdateJob(w http.ResponseWriter, r *http.Request) {
j := &Job{}
err := json.NewDecoder(r.Body).Decode(j)
if err != nil {
return
}
o := database.DB.Model(&j).Where("id = ?", j.ID).Update(&j)
render.JSON(w, r, o)
}
这两件事与 gorm 关联,我认为它们在其文档中没有得到充分传达,这继续让开发人员感到困惑。
1) 它使用 ID 来标识关联的实体
当您更新该技能列表时,如果它们中没有要 gorm 的 ID,它们只是要添加的新实体。这会导致您的重复值。即使你有一个 unique
字段,如果该字段不是实体的主键,那么 gorm 将尝试创建一个新记录并导致违反约束。
有几种方法可以解决这个问题:
- 确保 API 用户必须提供相关实体的 ID
- 通过用户提供的其他一些代理键从数据库中提取实体 ID,并将它们填充到您要保存的实体中。在您的情况下,可能是
name
,因为它是独一无二的。
- 将代理键设为主键(将
name
设为 Skill
结构的 gorm:"primaryKey"
)。
2)更新时,不会删除关联切片中未出现的现有关联
当您调用 Save/Update 时,gorm 不会删除集合关联的 far 端的实体。这是一项安全功能,可避免意外删除简单 Save/Update 上的数据。您必须明确表示想要这种行为。
要解决这个问题,您可以使用关联模式替换集合作为更新的一部分:db.Model(&job).Association('Skills').Replace(&job.Skills)
。
详细说明 Eziquel 的答案并展示对我有用的答案。
我更新了我的 Skill 结构以使用 Name
作为主键
type Skill struct {
Name string `json:"name,omitempty" gorm:"primary_key"`
}
然后我阅读了一些文档说只使用 gorm.DB.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&j)
不确定它的作用,但没有它,关联会重复或不更新。
func UpdateJob(w http.ResponseWriter, r *http.Request) {
j := &Job{}
err := json.NewDecoder(r.Body).Decode(j)
if err != nil {
return
}
database.DB.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&j)
database.DB.Model(&j).Association("Skills").Replace(&j.Skills)
render.JSON(w, r, &j)
}
我不知道database.DB.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&j)
是做什么的,但是我看到Jihnzu在文档中说要使用它,没有进一步解释。将其与 .Association("Skills").Replace(&j.Skills)
结合使用可确保没有重复项并且关联会更新。
如果不止一个你会做:
database.DB.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&j)
database.DB.Model(&j).Association("Skills").Replace(&j.Skills)
database.DB.Model(&j).Association("Locations").Replace(&j.Locations)
我正在尝试在我的演示 API 中,在 Job
和技能列表 []Skill
之间建立多对多关系。
工作结构
type Job struct {
ID string `sql:"type:uuid;primary_key;"`
Title string `json:"title,omitempty"`
Skills []*skill.Skill `json:"skills,omitempty"gorm:"many2many:job_skill;"`
CreatedAt time.Time
UpdatedAt time.Time
}
技能结构
type Skill struct {
gorm.Model
Name string `json:"name,omitempty"`
}
然后我使用 gorm.DB.AutoMigrate()
自动生成连接 table。
当我向我的 API 发送 POST
请求时,数据在第一时间正确创建,并且连接 table 如您所料填充。
示例POST
数据
{
"title": "Senior Python Engineer",
"skills": [{"name": "javascript"}, {"name": "python"}]
}
但是当我发送 PATCH
添加新技能的请求时,它会在技能 table 中复制该技能,然后在加入 table 中创建一条新记录已经存在的技能。
示例PATCH
数据
{
"title": "Lead Engineer",
"skills": [{"name": "javascript"}, {"name": "python"}, {"name": "management"}]
}
对数据进行获取请求将显示以下内容:
{
"title": "Lead Engineer",
"skills": [{"name": "javascript"}, {"name": "python"}, {"name": "javascript"}, {"name": "python"}, {"name": "management"}]
}
我也试过设置 gorm:"unique"
击中的技能 Name
但是当添加新技能时它失败了,因为它说其他两个已经存在,这很好但是不会添加新的。
我假设我只能发回新值?不是整个列表?
为清楚起见,我的一些 Go 代码
func GetJobs(w http.ResponseWriter, r *http.Request) {
j := &[]Job{}
o := database.DB.Preload("Skills").Find(&j)
render.JSON(w, r, o)
}
func UpdateJob(w http.ResponseWriter, r *http.Request) {
j := &Job{}
err := json.NewDecoder(r.Body).Decode(j)
if err != nil {
return
}
o := database.DB.Model(&j).Where("id = ?", j.ID).Update(&j)
render.JSON(w, r, o)
}
这两件事与 gorm 关联,我认为它们在其文档中没有得到充分传达,这继续让开发人员感到困惑。
1) 它使用 ID 来标识关联的实体
当您更新该技能列表时,如果它们中没有要 gorm 的 ID,它们只是要添加的新实体。这会导致您的重复值。即使你有一个 unique
字段,如果该字段不是实体的主键,那么 gorm 将尝试创建一个新记录并导致违反约束。
有几种方法可以解决这个问题:
- 确保 API 用户必须提供相关实体的 ID
- 通过用户提供的其他一些代理键从数据库中提取实体 ID,并将它们填充到您要保存的实体中。在您的情况下,可能是
name
,因为它是独一无二的。 - 将代理键设为主键(将
name
设为Skill
结构的gorm:"primaryKey"
)。
2)更新时,不会删除关联切片中未出现的现有关联
当您调用 Save/Update 时,gorm 不会删除集合关联的 far 端的实体。这是一项安全功能,可避免意外删除简单 Save/Update 上的数据。您必须明确表示想要这种行为。
要解决这个问题,您可以使用关联模式替换集合作为更新的一部分:db.Model(&job).Association('Skills').Replace(&job.Skills)
。
详细说明 Eziquel 的答案并展示对我有用的答案。
我更新了我的 Skill 结构以使用 Name
作为主键
type Skill struct {
Name string `json:"name,omitempty" gorm:"primary_key"`
}
然后我阅读了一些文档说只使用 gorm.DB.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&j)
不确定它的作用,但没有它,关联会重复或不更新。
func UpdateJob(w http.ResponseWriter, r *http.Request) {
j := &Job{}
err := json.NewDecoder(r.Body).Decode(j)
if err != nil {
return
}
database.DB.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&j)
database.DB.Model(&j).Association("Skills").Replace(&j.Skills)
render.JSON(w, r, &j)
}
我不知道database.DB.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&j)
是做什么的,但是我看到Jihnzu在文档中说要使用它,没有进一步解释。将其与 .Association("Skills").Replace(&j.Skills)
结合使用可确保没有重复项并且关联会更新。
如果不止一个你会做:
database.DB.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&j)
database.DB.Model(&j).Association("Skills").Replace(&j.Skills)
database.DB.Model(&j).Association("Locations").Replace(&j.Locations)