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)