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

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

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 :)

Hello my friend sorry my english

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")
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.