Fix create duplicated value when updating nested has many relationship, close #4796

This commit is contained in:
Jinzhu 2021-11-29 18:34:50 +08:00
parent 45e804dd3f
commit 27e2753c9d
5 changed files with 44 additions and 17 deletions

View File

@ -7,6 +7,7 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
"gorm.io/gorm/schema" "gorm.io/gorm/schema"
"gorm.io/gorm/utils"
) )
func SaveBeforeAssociations(create bool) func(db *gorm.DB) { func SaveBeforeAssociations(create bool) func(db *gorm.DB) {
@ -182,6 +183,7 @@ func SaveAfterAssociations(create bool) func(db *gorm.DB) {
fieldType = reflect.PtrTo(fieldType) fieldType = reflect.PtrTo(fieldType)
} }
elems := reflect.MakeSlice(reflect.SliceOf(fieldType), 0, 10) elems := reflect.MakeSlice(reflect.SliceOf(fieldType), 0, 10)
identityMap := map[string]bool{}
appendToElems := func(v reflect.Value) { appendToElems := func(v reflect.Value) {
if _, zero := rel.Field.ValueOf(v); !zero { if _, zero := rel.Field.ValueOf(v); !zero {
f := reflect.Indirect(rel.Field.ReflectValueOf(v)) f := reflect.Indirect(rel.Field.ReflectValueOf(v))
@ -197,10 +199,21 @@ func SaveAfterAssociations(create bool) func(db *gorm.DB) {
} }
} }
if isPtr { relPrimaryValues := make([]interface{}, 0, len(rel.FieldSchema.PrimaryFields))
elems = reflect.Append(elems, elem) for _, pf := range rel.FieldSchema.PrimaryFields {
} else { if pfv, ok := pf.ValueOf(elem); !ok {
elems = reflect.Append(elems, elem.Addr()) relPrimaryValues = append(relPrimaryValues, pfv)
}
}
cacheKey := utils.ToStringKey(relPrimaryValues)
if len(relPrimaryValues) == 0 || (len(relPrimaryValues) == len(rel.FieldSchema.PrimaryFields) && !identityMap[cacheKey]) {
identityMap[cacheKey] = true
if isPtr {
elems = reflect.Append(elems, elem)
} else {
elems = reflect.Append(elems, elem.Addr())
}
} }
} }
} }

View File

@ -178,19 +178,21 @@ func TestForeignKeyConstraintsBelongsTo(t *testing.T) {
} }
func TestFullSaveAssociations(t *testing.T) { func TestFullSaveAssociations(t *testing.T) {
coupon := &Coupon{
ID: "full-save-association-coupon1",
AppliesToProduct: []*CouponProduct{
{
CouponId: "full-save-association-coupon1",
ProductId: "full-save-association-product1",
},
},
AmountOff: 10,
PercentOff: 0.0,
}
err := DB. err := DB.
Session(&gorm.Session{FullSaveAssociations: true}). Session(&gorm.Session{FullSaveAssociations: true}).
Create(&Coupon{ Create(coupon).Error
ID: "full-save-association-coupon1",
AppliesToProduct: []*CouponProduct{
{
CouponId: "full-save-association-coupon1",
ProductId: "full-save-association-product1",
},
},
AmountOff: 10,
PercentOff: 0.0,
}).Error
if err != nil { if err != nil {
t.Errorf("Failed, got error: %v", err) t.Errorf("Failed, got error: %v", err)
@ -203,4 +205,9 @@ func TestFullSaveAssociations(t *testing.T) {
if DB.First(&CouponProduct{}, "coupon_id = ? AND product_id = ?", "full-save-association-coupon1", "full-save-association-product1").Error != nil { if DB.First(&CouponProduct{}, "coupon_id = ? AND product_id = ?", "full-save-association-coupon1", "full-save-association-product1").Error != nil {
t.Errorf("Failed to query saved association") t.Errorf("Failed to query saved association")
} }
orders := []Order{{Num: "order1", Coupon: coupon}, {Num: "order2", Coupon: coupon}}
if err := DB.Create(&orders).Error; err != nil {
t.Errorf("failed to create orders, got %v", err)
}
} }

View File

@ -427,7 +427,7 @@ func TestCompositePrimaryKeysAssociations(t *testing.T) {
DB.Migrator().DropTable(&Label{}, &Book{}) DB.Migrator().DropTable(&Label{}, &Book{})
if err := DB.AutoMigrate(&Label{}, &Book{}); err != nil { if err := DB.AutoMigrate(&Label{}, &Book{}); err != nil {
t.Fatalf("failed to migrate") t.Fatalf("failed to migrate, got %v", err)
} }
book := Book{ book := Book{

View File

@ -87,7 +87,7 @@ func OpenTestConnection() (db *gorm.DB, err error) {
func RunMigrations() { func RunMigrations() {
var err error var err error
allModels := []interface{}{&User{}, &Account{}, &Pet{}, &Company{}, &Toy{}, &Language{}, &Coupon{}, &CouponProduct{}} allModels := []interface{}{&User{}, &Account{}, &Pet{}, &Company{}, &Toy{}, &Language{}, &Coupon{}, &CouponProduct{}, &Order{}}
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
rand.Shuffle(len(allModels), func(i, j int) { allModels[i], allModels[j] = allModels[j], allModels[i] }) rand.Shuffle(len(allModels), func(i, j int) { allModels[i], allModels[j] = allModels[j], allModels[i] })

View File

@ -72,3 +72,10 @@ type CouponProduct struct {
CouponId string `gorm:"primarykey; size:255"` CouponId string `gorm:"primarykey; size:255"`
ProductId string `gorm:"primarykey; size:255"` ProductId string `gorm:"primarykey; size:255"`
} }
type Order struct {
gorm.Model
Num string
Coupon *Coupon
CouponID string
}