Gorm、外键和嵌入式结构

Cor*_*urn 9 foreign-keys go go-gorm

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:'{}'"`
}
Run Code Online (Sandbox Code Playgroud)

我的数据库中的每个模型都使用它,BaseModel如下所示:

type Profile struct {
    BaseModel

    Name   string `json:"name"`
    UserID string `json:"userId"`
}
Run Code Online (Sandbox Code Playgroud)

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

统一建模语言图

CreatedBy我正在尝试向和列添加外键UpdatedBy,以便它们必须指向现有的Profile. 所以我将以下字段添加到类型中BaseModel

    CreatedByProfile *Profile `json:"-" gorm:"foreignKey:CreatedBy"`
Run Code Online (Sandbox Code Playgroud)

我希望为每个模型创建外键,该模型BaseModel是该表的一部分并指向该Profiles表。然而,这只能让FK上场Profiles

统一建模语言图

问题的最小重现:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

如果我将该CreatedByProfile字段放在 上Activity,那么我会得到一个 FK,其中是 100% 向后的Profile.CreatedByFK 。Activity.ID

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

Activities []*Activity `json:"-" gorm:"foreignKey:CreatedBy"`
Run Code Online (Sandbox Code Playgroud)

它创建了我想要的 FK,但我实际上并不希望该字段出现在模型上。另外,我必须为数据库中的每个模型添加这个不必要的样板(并且_字段不会以 FK 的形式结束)。

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