首页 > 文章列表 > GORM 是否支持自动迁移具有循环关系的表?

GORM 是否支持自动迁移具有循环关系的表?

381 2024-02-19
问题内容

我一直在尝试在我们的 golang 项目中实现 gorm orm,但似乎有一个小问题。

其中一个结构具有循环依赖关系,因此现在当我尝试自动迁移来创建表时,我会收到错误,因为 gorm 正在尝试按顺序创建表。

示例原型:

message Person{
  optional string name= 1;
  optional Company company = 2;
}

message Company{
  optional string name= 1;
  optional Workers workers= 2;
}

message Workers {
  optional string name= 1;
  optional Person person= 2;
}

这是一个简单的示例,但正是我的循环依赖关系。 当我使用 gorm 插件生成原型时,它会生成包含所有 gorm 注释(包括 foregin 键)的模型。当然,当我尝试自动迁移它们时,它就会崩溃。

我发现解决这个问题的唯一方法是:

  1. 从“公司”中删除“工人”字段。
  2. 生成 gorm 模型。
  3. 运行自动迁移。
  4. 重构原始文件并将 workers 字段返回给公司。
  5. 运行自动迁移。
  6. 我们的所有表格都有相应的 fk。

我尝试在线搜索任何想法,但似乎找不到任何想法。

任何帮助/想法都值得赞赏!


正确答案


通过遵循这种方法,我能够实现您的需求。不幸的是,我对 proto 消息不太熟悉,所以我只分享您应该使用的相关 go 代码。如果我没记错的话,您在 proto 消息中定义的关联将被转换为 gorm 内的 belongsto 。否则,您应该使用 repeated 关键字(我说得对吗?)。
在前提之后,我将分享代码,然后是解释。

package main

import (
    "github.com/samber/lo"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

type Person struct {
    ID        int
    Name      string
    CompanyID *int
    Company   *Company
}

func (p Person) TableName() string {
    return "people"
}

type Company struct {
    ID       int
    Name     string
    WorkerID *int
    Worker   *Worker
}

type Worker struct {
    ID       int
    Name     string
    PersonID *int
    Person   *Person
}

func main() {
    dsn := "host=localhost port=54322 user=postgres password=postgres dbname=postgres sslmode=disable"
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
        DisableForeignKeyConstraintWhenMigrating: true,
    })
    if err != nil {
        panic(err)
    }

    db.AutoMigrate(&Person{}, &Company{}, &Worker{})
    db.Migrator().CreateConstraint(&Company{}, "Worker")
    db.Migrator().CreateConstraint(&Company{}, "fk_companies_people")
    db.Migrator().CreateConstraint(&Person{}, "Company")
    db.Migrator().CreateConstraint(&Person{}, "fk_people_companies")
    db.Migrator().CreateConstraint(&Worker{}, "Person")
    db.Migrator().CreateConstraint(&Worker{}, "fk_workers_people")

    db.Create(&Person{ID: 1, Name: "John", Company: &Company{ID: 1, Name: "ACME", Worker: &Worker{ID: 1, Name: "Worker 1"}}})
    db.Model(&Person{ID: 1}).Update("company_id", lo.ToPtr(1))
    db.Model(&Company{ID: 1}).Update("worker_id", lo.ToPtr(1))
    db.Model(&Worker{ID: 1}).Update("person_id", lo.ToPtr(1))

    // WRONG section!!!!!! uncomment any of these to try
    // db.Model(&Worker{ID: 1}).Update("person_id", lo.ToPtr(2)) // id "2" breaks as it doesn't exist
    // db.Model(&Person{ID: 1}).Update("company_id", lo.ToPtr(2)) // id "2" breaks as it doesn't exist
    // db.Model(&Company{ID: 1}).Update("worker_id", lo.ToPtr(2)) // id "2" breaks as it doesn't exist
}

好吧,让我引导您完成相关部分。

结构体定义

这里,您必须预测每个关联都可能是 null。这就是为什么我使用指针来定义它们。因此,您可以创建如下循环依赖项:

人=> 公司=> 工人=> 人=> ....

另外,我通过设置 people 覆盖了 struct person 的表名。也许 gorm 足够聪明,可以自己完成此操作,但从未尝试过。

sql 对象定义

当您实例化 gorm 客户端时,您必须确保在迁移时不会创建外键。为此,您必须将 gorm.config 结构中的 true 字段设置为 true 。因此,外键的创建取决于您。后者是通过您指定的 createconstraint 方法完成的:

  • 涉及的表
  • 上表涉及的关联
  • 您想如何命名外键约束

最后,您可以注意到我运行 automigrate 方法来创建没有外键的表。

书写逻辑

由于表的布局,insert 逻辑必须分为两部分。在第一个中,将记录插入到它们自己的表中(例如,将 person 插入 people 表中,将 company 插入 companies 中,依此类推)。我们故意将外键保留为 null,否则我们会出错。如果尚未插入相关记录,第一个总是会引发错误。
然后,我们使用 update 方法将每个外键设置为正确的值。

最终想法

我在代码中留下了一些注释语句来证明,如果您尝试分配一些不存在的值作为外键,它就会崩溃。这意味着您可以在这些列中插入 null 或正确的值。
我使用这个包 "github.com/samber/lo" 轻松获取从文字开始的指针值(例如 1)。

请告诉我这是否有助于解决您的问题,谢谢!