Your Question

假设有a、b、c三个模型,它们之间的关联关系如下:

type c struct {
    ID uint
    Bs []*b `gorm:"many2many:bs_cs"`
}
// c 有一个 BeforeCreate(填充 ID )
func (r *c) BeforeCreate(*gorm.DB) error {
    r.ID = uint(time.Now().Unix())
}

type b struct {
    ID uint
    Cs []*c `gorm:"many2many:bs_cs"`
}

type a struct {
    ID uint
    Bs []b
}

即 a Has Many b,b 和 c 为 Many to Many。

// 假设数据库已经有数条 c 记录
cs := make([]c, 0)
db.Find(&cs)
ptrs := make([]*c, len(cs))
for i, v := range cs {
    ptrs[i] = &c{ID: v.ID}
}

// bs
bs := []b{{Cs: ptrs}}

// 现在新增一条 a 
data := &a{
    ID: 1,
    Bs: bs,
}
db.Create(&data)

debug发现c的BeforeCreate竟然被触发。

The document you expected this should be explained

https://gorm.io/zh_CN/docs/many_to_many.html

Expected answer

预期的行为应该是:a、b表新增数据,并将b的数据与c的既有数据进行关联(即新增关联表bs_cs的数据),

而上述情形却被当作要新增c表的数据。

注释掉BeforeCreate后行为与预期相符。

很困惑,这是为何?

Comment From: black-06

Same as #6324

GORM doesn't know if the associated data exists, so Association.Append will try INSERT ... ON CONFLICT DO NOTHING.

Then the BeforeCreate hook is triggered.

Comment From: black-06

So you need to conditionally populate the id in the hook.

if r.ID != 0 {
    r.ID = uint(time.Now().Unix())
}

Comment From: congjunhua

So you need to conditionally populate the id in the hook.

go if r.ID != 0 { r.ID = uint(time.Now().Unix()) }

现在确实是通过在钩子中判断ID是否为空来决定是否执行ID填充,但本质上还是没有解决钩子函数一定会被执行的问题,而这个执行是不被期待的,感觉这徒增了心智负担。

我在考虑是不是要弃用钩子,但不使用钩子又会带来新问题,即钩子中的逻辑会被分散到多个地方,这又降低了可维护性,很纠结。

是否可以通过主键来判断数据是否存在呢?假设改表格有主键的话。

Comment From: black-06

Yes, Hook will be triggered unless use DB.Session(&gorm.Session{SkipHooks: true})

Or try to skip auto create/update, https://gorm.io/docs/associations.html#Skip-Auto-Create-x2F-Update

Comment From: github-actions[bot]

This issue has been automatically marked as stale because it has been open 360 days with no activity. Remove stale label or comment or this will be closed in 180 days