首页 > 解决方案 > Gorm 多对多关系重复值

问题描述

我试图在我的演示 API 中建立一个多对多的关系,在 aJob和一个技能列表之间[]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()自动生成连接表。

当我向我的 API 发送POST请求时,第一次会正确创建数据,并且连接表会按照您的预期填充。

示例POST数据

{
    "title": "Senior Python Engineer",      
    "skills": [{"name": "javascript"}, {"name": "python"}]
}

但是当我发送一个PATCH添加新技能的请求时,它会复制技能表中的技能,然后在连接表中为已经存在的技能创建一个新记录。

示例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)
}

标签: gogo-gorm

解决方案


这是我认为在其文档中没有充分传达 gorm 关联的两件事,这继续使开发人员感到困惑。

1) 它使用 ID 来识别关联实体

当您更新该技能列表时,如果他们没有 ID 来确认他们只是要添加的新实体。这会导致您的重复值。即使您有一个unique字段,如果该字段不是实体的主键,那么 gorm 将尝试创建新记录并导致违反约束。

有几种方法可以解决这个问题:

  • 确保 API 用户必须为相关实体提供 ID
  • 通过用户提供的其他代理键从数据库中提取实体 ID,并将其填充到您的要保存实体中。在您的情况下,这可能是name因为它是独一无二的。
  • 使该代理键成为您的主键(使namegorm:"primaryKey"Skill结构成为)。

2) 更新时,不会删除未出现在关联切片中的现有关联

当您调用保存/更新时,gorm 不会删除集合关联远端的实体。这是一项安全功能,可避免在简单的保存/更新时意外删除数据。您必须明确表示想要这种行为。

为了解决这个问题,您可以使用关联模式将集合替换为更新的一部分:db.Model(&job).Association('Skills').Replace(&job.Skills).


推荐阅读