gorm 中的外键约束
Foreign Key Constraint in gorm
我想创建一个 Booking-Class 关系,其中每个 Booking 只能分配一个 Class。
type Class struct {
Id int `json:"id"`
Name string `json:"name"`
}
type Booking struct {
Id int `json:"id"`
User string `json:"user"`
Members int `json:"members"`
ClassId int `json:"classid"`
}
我理解它类似于此处解释的 gorm 的“属于”关系 https://gorm.io/docs/belongs_to.html 但我想知道是否可以在不定义类型字段的情况下实现“外键约束” Class 在 Booking 模型中(仅 ClassId int)。为了确保使用不存在的 ClassId-s 我定义了函数:
func Find(slice []int, val int) bool {
for _, item := range slice {
if item == val {
return true
}
}
return false
}
func GetClassKeys(d *gorm.DB) []int {
var keys []int
rows, _ := d.Raw(`SELECT id
FROM classes`).Rows()
defer rows.Close()
for rows.Next() {
var key int
rows.Scan(&key)
keys = append(keys, key)
}
return keys
}
并在 creating/updating 预订之前执行此检查:
if !Find(GetClassKeys(db), booking.ClassId) {
log.Println("Wrong class id value")
return
}
但这不处理删除 class id 的情况(常规数据库会自动执行)。我想知道是否有一种方法可以通过简单地在用户模型中引用 Class 的主键来使用 gorm 实现正常的数据库外键功能?提前致谢
我认为 Migrator 工具不会帮助您,因为它假定您将使用默认的 Gorm 模式来定义关系,而您已明确决定不使用这些模式。
唯一剩下的选择是自己管理约束,可以通过 SQL 脚本或更简单的方式,一些自定义查询 运行 在 AutoMigrate
调用的同时:
import "gorm.io/gorm/clause"
// somewhere you must be calling
db.AutoMigrate(&Class{}, &Booking{})
// then you'd have:
constraint := "fk_booking_classid"
var count int64
err := db.Raw(
"SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = ? AND table_name = ? AND constraint_name = ?",
db.Migrator().CurrentDatabase(),
"bookings",
constraint,
).Scan(&count).Error
// handle error
if (count == 0) {
err := db.Exec(
"ALTER TABLE bookings ADD CONSTRAINT ? FOREIGN KEY class_id REFERENCES classes(id)",
clause.Table{Name: constraint},
).Error
// handle error
}
当然,这否定了自动迁移的优势(这意味着当字段名称等内容发生变化时,约束将在不更改迁移代码的情况下更新)。
我会看看为什么你不想像 gorm 期望的那样定义外键,因为这有点像你试图直接使用数据库模型作为你的 JSON API Request/Response 模型,这通常不会带来好的结果:)
我想创建一个 Booking-Class 关系,其中每个 Booking 只能分配一个 Class。
type Class struct {
Id int `json:"id"`
Name string `json:"name"`
}
type Booking struct {
Id int `json:"id"`
User string `json:"user"`
Members int `json:"members"`
ClassId int `json:"classid"`
}
我理解它类似于此处解释的 gorm 的“属于”关系 https://gorm.io/docs/belongs_to.html 但我想知道是否可以在不定义类型字段的情况下实现“外键约束” Class 在 Booking 模型中(仅 ClassId int)。为了确保使用不存在的 ClassId-s 我定义了函数:
func Find(slice []int, val int) bool {
for _, item := range slice {
if item == val {
return true
}
}
return false
}
func GetClassKeys(d *gorm.DB) []int {
var keys []int
rows, _ := d.Raw(`SELECT id
FROM classes`).Rows()
defer rows.Close()
for rows.Next() {
var key int
rows.Scan(&key)
keys = append(keys, key)
}
return keys
}
并在 creating/updating 预订之前执行此检查:
if !Find(GetClassKeys(db), booking.ClassId) {
log.Println("Wrong class id value")
return
}
但这不处理删除 class id 的情况(常规数据库会自动执行)。我想知道是否有一种方法可以通过简单地在用户模型中引用 Class 的主键来使用 gorm 实现正常的数据库外键功能?提前致谢
我认为 Migrator 工具不会帮助您,因为它假定您将使用默认的 Gorm 模式来定义关系,而您已明确决定不使用这些模式。
唯一剩下的选择是自己管理约束,可以通过 SQL 脚本或更简单的方式,一些自定义查询 运行 在 AutoMigrate
调用的同时:
import "gorm.io/gorm/clause"
// somewhere you must be calling
db.AutoMigrate(&Class{}, &Booking{})
// then you'd have:
constraint := "fk_booking_classid"
var count int64
err := db.Raw(
"SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = ? AND table_name = ? AND constraint_name = ?",
db.Migrator().CurrentDatabase(),
"bookings",
constraint,
).Scan(&count).Error
// handle error
if (count == 0) {
err := db.Exec(
"ALTER TABLE bookings ADD CONSTRAINT ? FOREIGN KEY class_id REFERENCES classes(id)",
clause.Table{Name: constraint},
).Error
// handle error
}
当然,这否定了自动迁移的优势(这意味着当字段名称等内容发生变化时,约束将在不更改迁移代码的情况下更新)。
我会看看为什么你不想像 gorm 期望的那样定义外键,因为这有点像你试图直接使用数据库模型作为你的 JSON API Request/Response 模型,这通常不会带来好的结果:)