Gorm,外键和嵌入式结构

发布于 2025-01-18 19:19:48 字数 4916 浏览 0 评论 0原文

Gorm 对外键的半生不熟、开箱即用的支持多年来一直令人烦恼,我终于试图一劳永逸地解决它。我正在使用 Postgres 12、gorm 1.23.3 和 go 1.18。

我有一个类似于 gorm.Model 的基本模型,但有一点额外:

type BaseModel struct {
    ID              string          `json:"id" gorm:"type:uuid;primarykey;default:uuid_generate_v4()"`
    InstanceVersion int             `json:"instanceVersion"`
    CreatedAt       time.Time       `json:"createdAt" gorm:"type:timestamp"`
    UpdatedAt       time.Time       `json:"updatedAt" gorm:"type:timestamp"`
    DeletedAt       *time.Time      `json:"deletedAt,omitempty" gorm:"type:timestamp" sql:"index"`
    CreatedBy       string          `json:"createdBy"`
    UpdatedBy       string          `json:"updatedBy"`
    DeletedBy       string          `json:"deletedBy,omitempty"`
    MetaData        json.RawMessage `json:"metadata" gorm:"type:jsonb;default:'{}'"`
}

我的数据库中的每个模型都使用此 BaseModel 如下:

type Profile struct {
    BaseModel

    Name   string `json:"name"`
    UserID string `json:"userId"`
}

它生成如下表(UML由 DBeaver 生成并仔细检查是否正确):

UML Diagram

我正在尝试向 CreatedByUpdatedBy 列添加外键,以便它们必须指向现有的 简介。因此,我将以下字段添加到 BaseModel 类型中:

    CreatedByProfile *Profile `json:"-" gorm:"foreignKey:CreatedBy"`

我希望为 BaseModel 所属的每个模型创建外键,并指向 配置文件表。但是,它仅在 Profiles 表上生成 FK。

UML 图

问题的最小重现:

package main

import (
    "encoding/json"
    "time"

    "github.com/lib/pq"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

type BaseModel struct {
    ID              string          `json:"id" gorm:"type:uuid;primarykey;default:uuid_generate_v4()"`
    InstanceVersion int             `json:"instanceVersion"`
    CreatedAt       time.Time       `json:"createdAt" gorm:"type:timestamp"`
    UpdatedAt       time.Time       `json:"updatedAt" gorm:"type:timestamp"`
    DeletedAt       *time.Time      `json:"deletedAt,omitempty" gorm:"type:timestamp" sql:"index"`
    CreatedBy       string          `json:"createdBy"`
    UpdatedBy       string          `json:"updatedBy"`
    DeletedBy       *string         `json:"deletedBy,omitempty"`
    MetaData        json.RawMessage `json:"metadata" gorm:"type:jsonb;default:'{}'"`

    CreatedByProfile *Profile `json:"-" gorm:"foreignKey:CreatedBy"`
}

type ActivityType string

type Activity struct {
    Base BaseModel `gorm:"embedded"`

    Type                ActivityType   `json:"type"`
    Message             string         `json:"message"`
    Content             string         `json:"content"`
    ImageUrl            string         `json:"imageUrl"`
    DisplayProfileIds   pq.StringArray `json:"displayProfileIds" gorm:"type:uuid[]"`
    RecipientProfileIds pq.StringArray `json:"recipientProfileIds" gorm:"-"`

    // Preload
    ActivityProfiles []*ActivityProfile `json:"activityProfiles"` // has many
}

type ActivityProfile struct {
    Base BaseModel `gorm:"embedded"`

    ReadAt *time.Time `json:"readAt,omitempty" gorm:"type:timestamp" sql:"index"`
    Route  string     `json:"route"`

    ActivityID string `json:"activityId"`
    ProfileID  string `json:"profileId"`

    // Preload
    Activity *Activity `json:"activity"` // belongs to
    Profile  *Profile  `json:"profile"`  // belongs to
}

type Profile struct {
    BaseModel

    Name   string `json:"name"`
    UserID string `json:"userId"`
}

func main() {
    db, err := gorm.Open(postgres.Open("host=localhost port=5432 user=corey dbname=corey password= sslmode=disable"))
    if err != nil {
        panic(err)
    }

    models := []interface{}{
        &Activity{},
        &ActivityProfile{},
        &Profile{},
    }

    err = db.AutoMigrate(models...)
    if err != nil {
        panic(err)
    }
}

我也尝试使用 gorm:"embedded" 标签而不是嵌套结构,但没有帮助。 这个问题中没有任何帮助:DB.Model(. ..).AddForeignKey(...) 不再存在,db.Migrator().CreateConstraint(...) 行不起作用(它怎么知道FK 是哪一列?它与什么其他类型匹配的列?我必须运行这两行吗?这怎么可能工作?!?),并且我不想要 OnUpdateOnDelete 约束,只是外键。

如果我将 CreatedByProfile 字段放在 Activity 上,那么我会得到一个 FK,其中 Profile.CreatedByActivity.ID< 的 FK /code> 是 100% 倒退。

我可以将此字段添加到我的 Profile 模型中:

Activities []*Activity `json:"-" gorm:"foreignKey:CreatedBy"`

它会像我想要的那样创建 FK,但我实际上并不希望该字段出现在模型上。另外,我必须为数据库中的每个模型添加这个不必要的样板(并且 _ 字段不会最终生成 FK)。

如何让 Gorm 做简单的事情,比如创建外键,而不用未使用的字段装饰我的模型?

Gorm's half-baked, magic-out-the-box support of foreign keys has been an annoyance for years and I'm finally trying to figure it out once and for all. I'm using Postgres 12, gorm 1.23.3 and go 1.18.

I have a base model similar to gorm.Model but with a little bit extra:

type BaseModel struct {
    ID              string          `json:"id" gorm:"type:uuid;primarykey;default:uuid_generate_v4()"`
    InstanceVersion int             `json:"instanceVersion"`
    CreatedAt       time.Time       `json:"createdAt" gorm:"type:timestamp"`
    UpdatedAt       time.Time       `json:"updatedAt" gorm:"type:timestamp"`
    DeletedAt       *time.Time      `json:"deletedAt,omitempty" gorm:"type:timestamp" sql:"index"`
    CreatedBy       string          `json:"createdBy"`
    UpdatedBy       string          `json:"updatedBy"`
    DeletedBy       string          `json:"deletedBy,omitempty"`
    MetaData        json.RawMessage `json:"metadata" gorm:"type:jsonb;default:'{}'"`
}

Every model in my DB uses this BaseModel as follows:

type Profile struct {
    BaseModel

    Name   string `json:"name"`
    UserID string `json:"userId"`
}

It generates the tables as follows (UML generated by DBeaver and double checked to be true):

UML Diagram

I'm trying to add a foreign key to the CreatedBy and UpdatedBy columns such that they must point to an existing Profile. So I add the following field to the BaseModel type:

    CreatedByProfile *Profile `json:"-" gorm:"foreignKey:CreatedBy"`

I expected the foreign key to be created for every model that BaseModel is a part of and point back to the Profiles table. However, it only makes the FK on the Profiles table.

UML Diagram

Minimal recreation of the problem:

package main

import (
    "encoding/json"
    "time"

    "github.com/lib/pq"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

type BaseModel struct {
    ID              string          `json:"id" gorm:"type:uuid;primarykey;default:uuid_generate_v4()"`
    InstanceVersion int             `json:"instanceVersion"`
    CreatedAt       time.Time       `json:"createdAt" gorm:"type:timestamp"`
    UpdatedAt       time.Time       `json:"updatedAt" gorm:"type:timestamp"`
    DeletedAt       *time.Time      `json:"deletedAt,omitempty" gorm:"type:timestamp" sql:"index"`
    CreatedBy       string          `json:"createdBy"`
    UpdatedBy       string          `json:"updatedBy"`
    DeletedBy       *string         `json:"deletedBy,omitempty"`
    MetaData        json.RawMessage `json:"metadata" gorm:"type:jsonb;default:'{}'"`

    CreatedByProfile *Profile `json:"-" gorm:"foreignKey:CreatedBy"`
}

type ActivityType string

type Activity struct {
    Base BaseModel `gorm:"embedded"`

    Type                ActivityType   `json:"type"`
    Message             string         `json:"message"`
    Content             string         `json:"content"`
    ImageUrl            string         `json:"imageUrl"`
    DisplayProfileIds   pq.StringArray `json:"displayProfileIds" gorm:"type:uuid[]"`
    RecipientProfileIds pq.StringArray `json:"recipientProfileIds" gorm:"-"`

    // Preload
    ActivityProfiles []*ActivityProfile `json:"activityProfiles"` // has many
}

type ActivityProfile struct {
    Base BaseModel `gorm:"embedded"`

    ReadAt *time.Time `json:"readAt,omitempty" gorm:"type:timestamp" sql:"index"`
    Route  string     `json:"route"`

    ActivityID string `json:"activityId"`
    ProfileID  string `json:"profileId"`

    // Preload
    Activity *Activity `json:"activity"` // belongs to
    Profile  *Profile  `json:"profile"`  // belongs to
}

type Profile struct {
    BaseModel

    Name   string `json:"name"`
    UserID string `json:"userId"`
}

func main() {
    db, err := gorm.Open(postgres.Open("host=localhost port=5432 user=corey dbname=corey password= sslmode=disable"))
    if err != nil {
        panic(err)
    }

    models := []interface{}{
        &Activity{},
        &ActivityProfile{},
        &Profile{},
    }

    err = db.AutoMigrate(models...)
    if err != nil {
        panic(err)
    }
}

I've also tried using gorm:"embedded" tags instead of nested structs but it didn't help. Nothing in this question helps: DB.Model(...).AddForeignKey(...) doesn't exist anymore, the db.Migrator().CreateConstraint(...) lines don't work (how does it know what column is the FK and which column it matches to of what other type? Do I have to run both lines? How could this ever possibly work?!?), and I don't want OnUpdate or OnDelete constraints, just foreign keys.

If I put the CreatedByProfile field on Activity, then I get a FK where Profile.CreatedBy is an FK to Activity.ID which is 100% backwards.

I can add this field to my Profile model:

Activities []*Activity `json:"-" gorm:"foreignKey:CreatedBy"`

and it creates the FK like I want, but I don't actually want that field present on the model. Plus, I'd have to add this unnecessary boilerplate for every model in my DB (and _ fields don't end up with the FK being made).

How can I make Gorm do simple things like create foreign keys without decorating my models with unused fields?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文