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:'{}'"`
}
我的数据库中的每个模型都使用此 BaseModel
如下:
type Profile struct {
BaseModel
Name string `json:"name"`
UserID string `json:"userId"`
}
它生成如下表(UML由 DBeaver 生成并仔细检查是否正确):
我正在尝试向 CreatedBy
和 UpdatedBy
列添加外键,以便它们必须指向现有的 简介
。因此,我将以下字段添加到 BaseModel
类型中:
CreatedByProfile *Profile `json:"-" gorm:"foreignKey:CreatedBy"`
我希望为 BaseModel
所属的每个模型创建外键,并指向 配置文件表。但是,它仅在
Profiles
表上生成 FK。
问题的最小重现:
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 是哪一列?它与什么其他类型匹配的列?我必须运行这两行吗?这怎么可能工作?!?),并且我不想要 OnUpdate
或 OnDelete
约束,只是外键。
如果我将 CreatedByProfile
字段放在 Activity
上,那么我会得到一个 FK,其中 Profile.CreatedBy
是 Activity.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):
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.
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论