如何使用GORM(Go)处理级联操作

huangapple go评论90阅读模式
英文:

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的自定义函数。

希望这对你有帮助 如何使用GORM(Go)处理级联操作

英文:

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 如何使用GORM(Go)处理级联操作

huangapple
  • 本文由 发表于 2016年4月6日 23:44:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/36456019.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定