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,6 +199,16 @@ func SaveAfterAssociations(create bool) func(db *gorm.DB) {
} }
} }
relPrimaryValues := make([]interface{}, 0, len(rel.FieldSchema.PrimaryFields))
for _, pf := range rel.FieldSchema.PrimaryFields {
if pfv, ok := pf.ValueOf(elem); !ok {
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 { if isPtr {
elems = reflect.Append(elems, elem) elems = reflect.Append(elems, elem)
} else { } else {
@ -205,6 +217,7 @@ func SaveAfterAssociations(create bool) func(db *gorm.DB) {
} }
} }
} }
}
switch db.Statement.ReflectValue.Kind() { switch db.Statement.ReflectValue.Kind() {
case reflect.Slice, reflect.Array: case reflect.Slice, reflect.Array:

View File

@ -178,9 +178,7 @@ func TestForeignKeyConstraintsBelongsTo(t *testing.T) {
} }
func TestFullSaveAssociations(t *testing.T) { func TestFullSaveAssociations(t *testing.T) {
err := DB. coupon := &Coupon{
Session(&gorm.Session{FullSaveAssociations: true}).
Create(&Coupon{
ID: "full-save-association-coupon1", ID: "full-save-association-coupon1",
AppliesToProduct: []*CouponProduct{ AppliesToProduct: []*CouponProduct{
{ {
@ -190,7 +188,11 @@ func TestFullSaveAssociations(t *testing.T) {
}, },
AmountOff: 10, AmountOff: 10,
PercentOff: 0.0, PercentOff: 0.0,
}).Error }
err := DB.
Session(&gorm.Session{FullSaveAssociations: true}).
Create(coupon).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
}