Repository: go-gorm/gorm

Issue #6324: BeforeCreate and Many2Many collide.
Created: 2023-05-16T13:21:47Z
GORM Playground Link
Here is my playground link: go-gorm/playground#596. I've reproduced the models.go and main_test.go below:
main_test.go:
func TestGORM(t *testing.T) {
	user := User{Name: "jinzhu"}

	DB.Create(&user)

	var result User
	if err := DB.First(&result, user.ID).Error; err != nil {
		t.Errorf("Failed, got error: %v", err)
	}

	familiar := Familiar{Name: "fam"}
	familiarIDBefore := familiar.Id
	DB.Create(&familiar)
	familiarIDAfter := familiar.Id
	if familiarIDBefore == familiarIDAfter {
		t.Errorf("Familiar ID should be created")
	}

	meeting := Meeting{IsLive: true}
	DB.Create(&meeting)
	familiars := []Familiar{familiar}
	meetingFamiliars := meeting.Familiars
	if len(meetingFamiliars) > 0 {
		t.Errorf("Meeting should not have any familiars yet")
	}

	DB.Model(&meeting).Association("Familiars").Append(familiars)
	meetingFamiliars = meeting.Familiars
	if len(meetingFamiliars) != 1 {
		t.Errorf("Meeting should have a familiar now")
	}

	familiarInMeetingID := meetingFamiliars[0].Id

	familiarIDAfterMeetingAdd := familiar.Id
	if familiarIDAfterMeetingAdd != familiarIDAfter {
		t.Errorf("Familiar ID should not be changed")
	}

	if familiarInMeetingID != familiarIDAfter {
		t.Errorf("Familiar ID should be the same. Meeting familiar ID: %v, familiar ID: %v", familiarInMeetingID, familiarIDAfter)
	}
}

models.go:
type Language struct {
	Code string `gorm:"primarykey"`
	Name string
}

type Meeting struct {
	gorm.Model
	Id          string     `gorm:"type:uuid;primaryKey" json:"id"`
	IsLive      bool       `json:"isLive"`
	CompletedAt time.Time  `json:"completedAt"`
	Familiars   []Familiar `gorm:"many2many:familiar_meetings;"`
}

func (m *Meeting) BeforeCreate(tx *gorm.DB) (err error) {
	// UUID version 4
	m.Id = uuid.NewString()
	return
}

type Familiar struct {
	gorm.Model
	Id       string    `gorm:"type:uuid;primaryKey" json:"id"`
	Meetings []Meeting `gorm:"many2many:familiar_meetings;"`
	Name     string    `json:"name"`
}

func (f *Familiar) BeforeCreate(tx *gorm.DB) (err error) {
	// UUID version 4
	f.Id = uuid.NewString()
	return
}

Description
The issue is that BeforeCreate is called on children being added to a many2many. Because I'm using BeforeCreate to do uuids, this is changing the Id of the object being added to the many2many.
Besides the bug, is there a better way to do what I'm trying to do? I'm new to gorm.

Duplicate issue #6356: Many to Many关联模式下关于BeforeCreate的疑惑
Created: 2023-05-28T18:15:10Z
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后行为与预期相符。
很困惑，这是为何？

Closing comment (2023-05-30T13:06:47Z): 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.