英文:
How treat cascade operation with GORM (Go)
问题
我正在测试Go的GORM库。
我发现这个库特别有用,逐步地我会使用越来越复杂的概念。
我正在面临级联操作管理的问题。
在某些问题上,创建者建议使用AfterDelete。
问题是:在After/BeforeDelete函数中,嵌套的项不存在。
有没有人有一个好的实现方法?
以下是我的代码(如果有人正在发现Gorm,几乎可以工作):
package main
import (
"time"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"fmt"
"github.com/satori/go.uuid"
)
type Company struct {
ID string `gorm:"primary_key;column:ID"`
Name string `sql:"size:255;unique;index" gorm:"column:Name"`
Employees []Employee // one-to-many relationship
Address Address // one-to-one relationship
}
func (u Company) TableName() string {
return "Company"
}
func (u Company) String() string {
return fmt.Sprintf("ID: %s | Name: %s | Employees: %v | Address: %v ", u.ID, u.Name, u.Employees, u.Address)
}
func (u *Company) BeforeCreate(scope *gorm.Scope) error {
scope.SetColumn("ID", uuid.NewV4().String())
return nil
}
func (u *Company) BeforeDelete(scope *gorm.Scope) error {
fmt.Println("BeforeDelete")
fmt.Println(u)
return nil
}
func (u *Company) AfterDelete(scope *gorm.Scope) error {
fmt.Println("AfterDelete")
fmt.Println(u)
return nil
}
type Employee struct {
ID string `gorm:"primary_key;column:ID"`
FirstName string `gorm:"column:FirstName"`
LastName string `gorm:"column:LastName"`
SocialSecurityNo string `gorm:"column:SocialSecurityNo"`
DateOfBirth time.Time `sql:"DEFAULT:current_timestamp" gorm:"column:DateOfBirth"`
Deleted bool `sql:"DEFAULT:false" gorm:"column:Deleted"`
CompanyID string `gorm:"column:Company_ID"`
Roles []Role // one-to-many relationship
}
func (u Employee) TableName() string {
return "Employee"
}
func (u Employee) String() string {
return fmt.Sprintf("ID: %s | FirstName: %s | Roles: %v ", u.ID, u.FirstName, u.Roles)
}
func (u *Employee) BeforeCreate(scope *gorm.Scope) error {
scope.SetColumn("ID", uuid.NewV4().String())
return nil
}
type Role struct {
Name string `gorm:"column:Name"`
Code string `gorm:"column:Code"`
EmployeeID string `gorm:"column:Employee_ID"`
}
func (u Role) TableName() string {
return "Role"
}
func (u Role) String() string {
return fmt.Sprintf("Name: %s | Code: %s", u.Name, u.Code)
}
type Address struct {
Country string `gorm:"column:Country"`
City string `gorm:"column:City"`
PostCode string `gorm:"column:PostCode"`
Line1 string `gorm:"column:Line1"`
Line2 string `gorm:"column:Line2"`
CompanyID string `gorm:"column:Company_ID"`
}
func (u Address) TableName() string {
return "Address"
}
func main() {
db := getDBConnection()
//If needed, you can create the file and schemas with the line below
createTables(db)
testCRUD(db)
}
func getDBConnection() (db *gorm.DB) {
//Change the file location for your needs
db, err := gorm.Open("sqlite3", `C:\Users\jbricout\Desktop\TestORM.db`)
if err != nil {
panic(err)
}
// Ping function checks the database connectivity
err = db.DB().Ping()
if err != nil {
panic(err)
}
return db
}
func createTables(db *gorm.DB) {
if err := db.CreateTable(&Company{}).Error; err != nil {
checkErr(err)
}
if err := db.CreateTable(&Address{}).Error; err != nil {
checkErr(err)
}
if err := db.CreateTable(&Employee{}).Error; err != nil {
checkErr(err)
}
if err := db.CreateTable(&Role{}).Error; err != nil {
checkErr(err)
}
}
func testCRUD(db *gorm.DB) {
sampleCompany := getInitializedCompany()
fmt.Println("Insert...")
if err := db.Create(&sampleCompany).Error; err != nil {
checkErr(err)
}
fmt.Println("Insert done with id : ", sampleCompany.ID)
fmt.Println("Find Only Company (Lazy load)...")
var firstComp Company
if err := db.Where("ID = ?", sampleCompany.ID).First(&firstComp).Error; err != nil {
checkErr(err)
}
fmt.Println("Company : ", firstComp)
fmt.Println("Find done")
fmt.Println("Find Only Company (Eager load)...")
var fullComp Company
db.Preload("Employees.Roles").Preload("Address").First(&fullComp)
if err := db.Where("ID = ?", sampleCompany.ID).First(&fullComp).Error; err != nil {
checkErr(err)
}
fmt.Println("Company : ", fullComp)
fmt.Println("Find done")
fmt.Println("Update...")
firstComp.Name = "Google Plus"
if len(firstComp.Address.Country) > 0 {
firstComp.Address.Country = "France"
}
if err := db.Save(&firstComp).Error; err != nil {
checkErr(err)
}
fmt.Println("Update done")
transaction := db.Begin()
fmt.Println("Delete...")
if err := transaction.Delete(&firstComp).Error; err != nil {
checkErrTransaction(err, transaction)
}
transaction.Commit()
fmt.Println("Delete done")
}
func getInitializedCompany() Company {
return Company{
Name: "Google",
Address: Address{
Country: "USA",
City: "Moutain View",
PostCode: "1600",
Line1: "Cloverfield Lane, 32",
Line2: "Apt 64",
},
Employees: []Employee{
Employee{
FirstName: "John",
LastName: "Doe",
SocialSecurityNo: "00-000-0000",
Roles: []Role{
Role{
Name: "Metier 1",
Code: "MET1",
},
Role{
Name: "Metier 2",
Code: "MET2",
},
},
},
Employee{
FirstName: "James",
LastName: "Dean",
SocialSecurityNo: "00-000-0001",
Roles: []Role{
Role{
Name: "Metier 1",
Code: "MET1",
},
},
},
Employee{
FirstName: "Joan",
LastName: "Dutsch",
SocialSecurityNo: "00-000-0002",
Roles: []Role{
Role{
Name: "Metier 2",
Code: "MET3",
},
},
},
},
}
}
func checkErr(err error) {
if err != nil {
panic(err)
}
}
func checkErrTransaction(err error, transaction *gorm.DB) {
transaction.Rollback()
if err != nil {
panic(err)
}
}
希望对你有帮助!
英文:
I'm testing out Go's GORM lib.
I find this lib particularly useful and, step by step, I play with more and more complicated notions.
I'm facing the problem of cascading operation management.
On certain issues, the creator suggests to use the AfterDelete.
The problem is : in the After/BeforeDelete functions, nested items are not present.
Is everyone have a good way to implement this ?
Here is my code (almost working if someone is discovering Gorm) :
package main
import (
"time"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"fmt"
"github.com/satori/go.uuid"
)
type Company struct {
ID string `gorm:"primary_key;column:ID"`
Name string `sql:"size:255;unique;index" gorm:"column:Name"`
Employees []Employee // one-to-many relationship
Address Address // one-to-one relationship
}
func (u Company) TableName() string {
return "Company"
}
func (u Company) String() string {
return fmt.Sprintf("ID: %s | Name: %s | Employees: %v | Address: %v ", u.ID, u.Name, u.Employees, u.Address)
}
func (u *Company) BeforeCreate(scope *gorm.Scope) error {
scope.SetColumn("ID", uuid.NewV4().String())
return nil
}
func (u *Company) BeforeDelete(scope *gorm.Scope) error {
fmt.Println("BeforeDelete")
fmt.Println(u)
return nil
}
func (u *Company) AfterDelete(scope *gorm.Scope) error {
fmt.Println("AfterDelete")
fmt.Println(u)
return nil
}
type Employee struct {
ID string `gorm:"primary_key;column:ID"`
FirstName string `gorm:"column:FirstName"`
LastName string `gorm:"column:LastName"`
SocialSecurityNo string `gorm:"column:SocialSecurityNo"`
DateOfBirth time.Time `sql:"DEFAULT:current_timestamp" gorm:"column:DateOfBirth"`
Deleted bool `sql:"DEFAULT:false" gorm:"column:Deleted"`
CompanyID string `gorm:"column:Company_ID"`
Roles []Role // one-to-many relationship
}
func (u Employee) TableName() string {
return "Employee"
}
func (u Employee) String() string {
return fmt.Sprintf("ID: %s | FirstName: %s | Roles: %v ", u.ID, u.FirstName, u.Roles)
}
func (u *Employee) BeforeCreate(scope *gorm.Scope) error {
scope.SetColumn("ID", uuid.NewV4().String())
return nil
}
type Role struct {
Name string `gorm:"column:Name"`
Code string `gorm:"column:Code"`
EmployeeID string `gorm:"column:Employee_ID"`
}
func (u Role) TableName() string {
return "Role"
}
func (u Role) String() string {
return fmt.Sprintf("Name: %s | Code: %s", u.Name, u.Code)
}
type Address struct {
Country string `gorm:"column:Country"`
City string `gorm:"column:City"`
PostCode string `gorm:"column:PostCode"`
Line1 string `gorm:"column:Line1"`
Line2 string `gorm:"column:Line2"`
CompanyID string `gorm:"column:Company_ID"`
}
func (u Address) TableName() string {
return "Address"
}
func main() {
db := getDBConnection()
//If needed, you can create the file and schemas with the line below
createTables(db)
testCRUD(db)
}
func getDBConnection() (db *gorm.DB) {
//Change the file location for your needs
db, err := gorm.Open("sqlite3", `C:\Users\jbricout\Desktop\TestORM.db`)
if err != nil {
panic(err)
}
// Ping function checks the database connectivity
err = db.DB().Ping()
if err != nil {
panic(err)
}
return db
}
func createTables(db *gorm.DB) {
if err := db.CreateTable(&Company{}).Error; err != nil {
checkErr(err)
}
if err := db.CreateTable(&Address{}).Error; err != nil {
checkErr(err)
}
if err := db.CreateTable(&Employee{}).Error; err != nil {
checkErr(err)
}
if err := db.CreateTable(&Role{}).Error; err != nil {
checkErr(err)
}
}
func testCRUD(db *gorm.DB) {
sampleCompany := getInitializedCompany()
fmt.Println("Insert...")
if err := db.Create(&sampleCompany).Error; err != nil {
checkErr(err)
}
fmt.Println("Insert done with id : ", sampleCompany.ID)
fmt.Println("Find Only Company (Lazy load)...")
var firstComp Company
if err := db.Where("ID = ?", sampleCompany.ID).First(&firstComp).Error; err != nil {
checkErr(err)
}
fmt.Println("Company : ", firstComp)
fmt.Println("Find done")
fmt.Println("Find Only Company (Eager load)...")
var fullComp Company
db.Preload("Employees.Roles").Preload("Address").First(&fullComp)
if err := db.Where("ID = ?", sampleCompany.ID).First(&fullComp).Error; err != nil {
checkErr(err)
}
fmt.Println("Company : ", fullComp)
fmt.Println("Find done")
fmt.Println("Update...")
firstComp.Name = "Google Plus"
if len(firstComp.Address.Country) > 0 {
firstComp.Address.Country = "France"
}
if err := db.Save(&firstComp).Error; err != nil {
checkErr(err)
}
fmt.Println("Update done")
transaction := db.Begin()
fmt.Println("Delete...")
if err := transaction.Delete(&firstComp).Error; err != nil {
checkErrTransaction(err, transaction)
}
transaction.Commit()
fmt.Println("Delete done")
}
func getInitializedCompany() Company {
return Company{
Name: "Google",
Address: Address{
Country: "USA",
City: "Moutain View",
PostCode: "1600",
Line1: "Cloverfield Lane, 32",
Line2: "Apt 64",
},
Employees: []Employee{
Employee{
FirstName: "John",
LastName: "Doe",
SocialSecurityNo: "00-000-0000",
Roles: []Role{
Role{
Name: "Metier 1",
Code: "MET1",
},
Role{
Name: "Metier 2",
Code: "MET2",
},
},
},
Employee{
FirstName: "James",
LastName: "Dean",
SocialSecurityNo: "00-000-0001",
Roles: []Role{
Role{
Name: "Metier 1",
Code: "MET1",
},
},
},
Employee{
FirstName: "Joan",
LastName: "Dutsch",
SocialSecurityNo: "00-000-0002",
Roles: []Role{
Role{
Name: "Metier 2",
Code: "MET3",
},
},
},
},
}
}
func checkErr(err error) {
if err != nil {
panic(err)
}
}
func checkErrTransaction(err error, transaction *gorm.DB) {
transaction.Rollback()
if err != nil {
panic(err)
}
}
Thanks
答案1
得分: 4
执行级联排除操作时,您必须在表之间添加外键。
这是我使用的一个示例,其中任务历史记录与任务相关联。当我删除任务时,它已经删除了历史记录。
添加外键
// 添加外键
// 第一个参数:外键字段
// 第二个参数:目标表(id)
// 第三个参数:ONDELETE
// 第四个参数:ONUPDATE
db.Model(&User{}).AddForeignKey("city_id", "cities(id)", "RESTRICT", "RESTRICT")
我的示例:
db.Model(&models.TaskHistoric{}).AddForeignKey("task_uuid", "tasks(uuid)", "CASCADE", "CASCADE")
英文:
To perform cascade exclusion, you must add the foreign key between the tables.
This is an example I used where task history gets linked to tasks. When I delete the task, it already deletes the history.
Add Foreign Key
// Add foreign key
// 1st param : foreignkey field
// 2nd param : destination table(id)
// 3rd param : ONDELETE
// 4th param : ONUPDATE
db.Model(&User{}).AddForeignKey("city_id", "cities(id)", "RESTRICT", "RESTRICT")
My example:
db.Model(&models.TaskHistoric{}).AddForeignKey("task_uuid", "tasks(uuid)", "CASCADE", "CASCADE")
答案2
得分: 1
类型 Bucketlist 结构体 {
gorm.Model
名称 字符串 json:"name"
创建者 字符串 json:"created_by"
用户ID 无符号整数 json:"user_id"
项目 []BucketlistItem json:"item"
}
类型 BucketlistItem 结构体 {
gorm.Model
名称 字符串 json:"name"
完成 布尔值 json:"done"
BucketlistID 无符号整数 json:"bucketlist_id,omitempty"
}
// AfterDelete 钩子函数用于级联删除
func (bucketlist *Bucketlist) AfterDelete(tx *gorm.DB) 错误 {
返回 tx.Model(&BucketlistItem{}).Where("bucketlist_id = ?", bucketlist.ID).Unscoped().Delete(&BucketlistItem{}).Error
}
这对我有效。
英文:
type Bucketlist struct {
gorm.Model
Name string `json:"name"`
CreatedBy string `json:"created_by"`
UserID uint `json:"user_id"`
Item []BucketlistItem `json:"item"`
}
type BucketlistItem struct {
gorm.Model
Name string `json:"name"`
Done bool `json:"done"`
BucketlistID uint `json:"bucketlist_id,omitempty"`
}
// AfterDelete hook defined for cascade delete
func (bucketlist *Bucketlist) AfterDelete(tx *gorm.DB) error {
return tx.Model(&BucketlistItem{}).Where("bucketlist_id = ?", bucketlist.ID).Unscoped().Delete(&BucketlistItem{}).Error
}
This works for me
Context:
When a bucketlist model instance is deleted, the corresponding items(1 up to x) are also deleted using the AfterDelete hook.
答案3
得分: 0
我已经实现了这个解决方案来回应我的问题:
func DeleteContentCascade(content *Content, db *gorm.DB, debug bool) error {
if debug {
db = db.Debug()
}
for _, child := range content.Children {
DeleteChildCascade(&child, db, debug) //Custom method like the current
}
if err := db.Delete(content).Error; err != nil {
return err
}
return nil
}
对于我在数据库管理中的每个"Item"文件,我创建了一个名为DeleteCascade的自定义函数。
希望这对你有帮助
英文:
I have implemented this solution for responding to my problem :
func DeleteContentCascade(content *Content, db *gorm.DB, debug bool) error {
if debug {
db = db.Debug()
}
for _, child := range content.Children {
DeleteChildCascade(&child, db, debug) //Custom method like the current
}
if err := db.Delete(content).Error; err != nil {
return err
}
return nil
}
For every "Item" File in my DB management, I have created a custom function DeleteCascade.
I hope it'll help
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论