英文:
Using GORM with custom join table and foreign keys
问题
我正在使用GORM创建数据库模型。我有三个模型遇到了问题。它们是User、Email和UserEmail模型。我希望在User和Email模型之间建立多对多的关系,这样我就可以跟踪用户何时更改了电子邮件以及何时更改,而无需使用日志表。由于已经存在UserEmail模型,我认为User模型没有必要保存EmailID。否则,UserEmail就变成了一个典型的日志表。
UserEmail表应该只允许任何用户同时设置一个电子邮件。因此,我希望将UserID + DeletedAt字段设置为主键。这样,只能存在一个具有NULL DeletedAt的行。
我遇到的问题是,我运行的迁移命令导致多个错误。我尝试了很多其他方法,无法一一列举,但是我尝试的其他方法无法正确生成正确的外键。我认为我想在UserEmail中有两个外键,分别是EmailID和UserID。如果可能的话,我还希望GORM的Preload
功能能够正常工作。
我有一个迁移命令,运行时实际上运行了以下代码:
func main() {
app.DB.Migrator().DropTable(&User{})
app.DB.Migrator().CreateTable(&User{})
app.DB.Migrator().DropTable(&Email{})
app.DB.Migrator().CreateTable(&Email{})
app.DB.Migrator().DropTable(&UserEmail{})
app.DB.Migrator().CreateTable(&UserEmail{})
}
我还尝试按不同的顺序迁移每个模型。然而,每次都会出现错误。Email和UserEmail表被正确创建,但是User总是出现一些错误。
2023/07/21 16:38:50 Dropping table named 'users'...
2023/07/21 16:38:50 [...]/go/pkg/mod/gorm.io/driver/mysql@v1.3.6/migrator.go:126
[error] invalid field found for struct github.com/neekla/pmapi/api/database/model.User's field Email: define a valid foreign key for relations or implement the Valuer/Scanner interface
2023/07/21 16:38:50 [...]/go/pkg/mod/gorm.io/driver/mysql@v1.3.6/migrator.go:126
[error] failed to parse value &model.User{ID:0x0, Email:model.Email{ID:0x0, Address:"", IsBanned:false, IsRegistered:false, IsVerified:false, CreatedAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), UpdatedAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), DeletedAt:(*gorm.DeletedAt)(nil)}, PreviousEmails:[]model.Email(nil), Name:"", PasswordHash:"", CreatedAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), UpdatedAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), DeletedAt:(*gorm.DeletedAt)(nil)}, got error invalid field found for struct github.com/neekla/pmapi/api/database/model.User's field Email: define a valid foreign key for relations or implement the Valuer/Scanner interface
2023/07/21 16:38:50 [...]/go/pkg/mod/gorm.io/driver/mysql@v1.3.6/migrator.go:128
[0.515ms] [rows:0] SET FOREIGN_KEY_CHECKS = 0;
2023/07/21 16:38:50 [...]/go/pkg/mod/gorm.io/driver/mysql@v1.3.6/migrator.go:130
[error] invalid field found for struct github.com/neekla/pmapi/api/database/model.User's field Email: define a valid foreign key for relations or implement the Valuer/Scanner interface
2023/07/21 16:38:50 Creating table named 'users'...
2023/07/21 16:38:50 [...]/api/command/commandMigrate.go:132
[error] invalid field found for struct github.com/neekla/pmapi/api/database/model.User's field Email: define a valid foreign key for relations or implement the Valuer/Scanner interface
2023/07/21 16:38:50 [...]/api/command/commandMigrate.go:132
[error] failed to parse value &model.User{ID:0x0, Email:model.Email{ID:0x0, Address:"", IsBanned:false, IsRegistered:false, IsVerified:false, CreatedAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), UpdatedAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), DeletedAt:(*gorm.DeletedAt)(nil)}, PreviousEmails:[]model.Email(nil), Name:"", PasswordHash:"", CreatedAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), UpdatedAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), DeletedAt:(*gorm.DeletedAt)(nil)}, got error invalid field found for struct github.com/neekla/pmapi/api/database/model.User's field Email: define a valid foreign key for relations or implement the Valuer/Scanner interface
2023/07/21 16:38:50 [...]/api/command/commandMigrate.go:132
[error] invalid field found for struct github.com/neekla/pmapi/api/database/model.User's field Email: define a valid foreign key for relations or implement the Valuer/Scanner interface
以下是当前模型的样子,可以看到所需的功能。
func init() {
app.DB.SetupJoinTable(&User{}, "Emails", &UserEmail{})
}
// 用户表不应该有任何EmailID列,这就是UserEmail表的作用,但是如果能够预加载电子邮件值,那将是很好的
// Email字段应该是UserEmails中具有NULL DeletedAt的电子邮件
// PreviousEmails字段应该是UserEmails中具有非NULL DeletedAt的所有电子邮件
type User struct {
ID uint `json:"id" gorm:"primarykey"`
Email Email `json:"email"`
PreviousEmails []Email `json:"previous_emails"`
Name string `json:"name" gorm:"type:varchar(255);not null"`
PasswordHash string `json:"password_hash,omitempty" gorm:"type:binary(60);not null"`
CreatedAt time.Time `json:"created_at" gorm:"type:DATETIME;default:CURRENT_TIMESTAMP;not null"`
UpdatedAt time.Time `json:"updated_at" gorm:"type:DATETIME"`
DeletedAt *gorm.DeletedAt `json:"deleted_at" gorm:"type:DATETIME;index"`
}
func (User) TableName() string {
return "users"
}
// 邮件不会被删除,它们会永久存储
type Email struct {
ID uint `json:"id" gorm:"primarykey"`
Address string `json:"address" gorm:"type:varchar(320);unique;not null"`
IsBanned bool `json:"is_banned" gorm:"type:bit;default:0;not null"`
IsRegistered bool `json:"is_registered" gorm:"type:bit;default:0;not null"`
IsVerified bool `json:"is_verified" gorm:"type:bit;default:0;not null"`
CreatedAt time.Time `json:"created_at" gorm:"type:DATETIME;default:CURRENT_TIMESTAMP;not null"`
UpdatedAt time.Time `json:"updated_at" gorm:"type:DATETIME"`
}
func (Email) TableName() string {
return "emails"
}
// 当用户更改其电子邮件时,旧电子邮件会被软删除
type UserEmail struct {
EmailID uint `json:"email_id"`
UserID uint `json:"user_id" gorm:"primarykey"`
CreatedAt time.Time `json:"created_at" gorm:"type:DATETIME;default:CURRENT_TIMESTAMP;not null"`
DeletedAt *gorm.DeletedAt `json:"deleted_at" gorm:"primarykey;type:DATETIME"`
}
func (UserEmail) TableName() string {
return "user_emails"
}
如何实现所需的效果?
英文:
I'm using GORM to create database models. I have three models that I am having troubles with. There are User, Email, and UserEmail models. I want a many2many relationship between the User and Email models, so that I can keep track of when a users change an email and when, without having to use log table. Since the UserEmail model exists, I see no reason for the User model to hold an EmailID. Otherwise, the UserEmail just becomes a typical log table.
The UserEmail table should only allow any User to have one email set at a time. For that reason, I want to se the UserID + DeletedAt fields to be the primary key. That way, only one row with a NULL DeletedAt can exist.
The problem I am having is that the migrator commands I am running is causing multiple errors. I've tried so many other things that cannot recount everything, however the other things I have tried wouldn't properly produce the correct foreign keys. I think I want two foreign keys in UserEmail for EmailID and UserID. I would also like GORM's Preload
functionality should work if possible.
I have a migrate command, that when run, it essentially runs this:
func main() {
app.DB.Migrator().DropTable(&User{})
app.DB.Migrator().CreateTable(&User{})
app.DB.Migrator().DropTable(&Email{})
app.DB.Migrator().CreateTable(&Email{})
app.DB.Migrator().DropTable(&UserEmail{})
app.DB.Migrator().CreateTable(&UserEmail{})
}
I have also tried migrating each model in different orders. However every time, I get errors. The Email and UserEmail tables get created properly, however User always has some errors.
2023/07/21 16:38:50 Dropping table named 'users'...
2023/07/21 16:38:50 [...]/go/pkg/mod/gorm.io/driver/mysql@v1.3.6/migrator.go:126
[error] invalid field found for struct github.com/neekla/pmapi/api/database/model.User's field Email: define a valid foreign key for relations or implement the Valuer/Scanner interface
2023/07/21 16:38:50 [...]/go/pkg/mod/gorm.io/driver/mysql@v1.3.6/migrator.go:126
[error] failed to parse value &model.User{ID:0x0, Email:model.Email{ID:0x0, Address:"", IsBanned:false, IsRegistered:false, IsVerified:false, CreatedAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), UpdatedAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), DeletedAt:(*gorm.DeletedAt)(nil)}, PreviousEmails:[]model.Email(nil), Name:"", PasswordHash:"", CreatedAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), UpdatedAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), DeletedAt:(*gorm.DeletedAt)(nil)}, got error invalid field found for struct github.com/neekla/pmapi/api/database/model.User's field Email: define a valid foreign key for relations or implement the Valuer/Scanner interface
2023/07/21 16:38:50 [...]/go/pkg/mod/gorm.io/driver/mysql@v1.3.6/migrator.go:128
[0.515ms] [rows:0] SET FOREIGN_KEY_CHECKS = 0;
2023/07/21 16:38:50 [...]/go/pkg/mod/gorm.io/driver/mysql@v1.3.6/migrator.go:130
[error] invalid field found for struct github.com/neekla/pmapi/api/database/model.User's field Email: define a valid foreign key for relations or implement the Valuer/Scanner interface
2023/07/21 16:38:50 Creating table named 'users'...
2023/07/21 16:38:50 [...]/api/command/commandMigrate.go:132
[error] invalid field found for struct github.com/neekla/pmapi/api/database/model.User's field Email: define a valid foreign key for relations or implement the Valuer/Scanner interface
2023/07/21 16:38:50 [...]/api/command/commandMigrate.go:132
[error] failed to parse value &model.User{ID:0x0, Email:model.Email{ID:0x0, Address:"", IsBanned:false, IsRegistered:false, IsVerified:false, CreatedAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), UpdatedAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), DeletedAt:(*gorm.DeletedAt)(nil)}, PreviousEmails:[]model.Email(nil), Name:"", PasswordHash:"", CreatedAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), UpdatedAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), DeletedAt:(*gorm.DeletedAt)(nil)}, got error invalid field found for struct github.com/neekla/pmapi/api/database/model.User's field Email: define a valid foreign key for relations or implement the Valuer/Scanner interface
2023/07/21 16:38:50 [...]/api/command/commandMigrate.go:132
[error] invalid field found for struct github.com/neekla/pmapi/api/database/model.User's field Email: define a valid foreign key for relations or implement the Valuer/Scanner interface
Here is what the current models look like, which gives a glimpse the desired functionality.
func init() {
app.DB.SetupJoinTable(&User{}, "Emails", &UserEmail{})
}
// the user table should not have any EmailID column, that's what the UserEmail table is for, but it would be nice to still be able to preload the email values
// Email field should be the email in UserEmails that has NULL for for DeletedAt
// PreviousEmails field should be all the emails in UserEmails that have a non-NULL DeletedAt
type User struct {
ID uint `json:"id" gorm:"primarykey"`
Email Email `json:"email"`
PreviousEmails []Email `json:"previous_emails"`
Name string `json:"name" gorm:"type:varchar(255);not null"`
PasswordHash string `json:"password_hash,omitempty" gorm:"type:binary(60);not null"`
CreatedAt time.Time `json:"created_at" gorm:"type:DATETIME;default:CURRENT_TIMESTAMP;not null"`
UpdatedAt time.Time `json:"updated_at" gorm:"type:DATETIME"`
DeletedAt *gorm.DeletedAt `json:"deleted_at" gorm:"type:DATETIME;index"`
}
func (User) TableName() string {
return "users"
}
// emails do not get deleted, they are permanently stored
type Email struct {
ID uint `json:"id" gorm:"primarykey"`
Address string `json:"address" gorm:"type:varchar(320);unique;not null"`
IsBanned bool `json:"is_banned" gorm:"type:bit;default:0;not null"`
IsRegistered bool `json:"is_registered" gorm:"type:bit;default:0;not null"`
IsVerified bool `json:"is_verified" gorm:"type:bit;default:0;not null"`
CreatedAt time.Time `json:"created_at" gorm:"type:DATETIME;default:CURRENT_TIMESTAMP;not null"`
UpdatedAt time.Time `json:"updated_at" gorm:"type:DATETIME"`
}
func (Email) TableName() string {
return "emails"
}
// when a user changes their email, the old one gets soft deleted
type UserEmail struct {
EmailID uint `json:"email_id"`
UserID uint `json:"user_id" gorm:"primarykey"`
CreatedAt time.Time `json:"created_at" gorm:"type:DATETIME;default:CURRENT_TIMESTAMP;not null"`
DeletedAt *gorm.DeletedAt `json:"deleted_at" gorm:"primarykey;type:DATETIME"`
}
func (UserEmail) TableName() string {
return "user_emails"
}
How can I achieve the desired effects?
答案1
得分: 0
我修复了。很简单。我只需要运行以下命令:
go get gorm.io/gorm@none
英文:
I fixed it. It was simple. All I had to do was run:
go get gorm.io/gorm@none
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论