mirror of https://github.com/go-gorm/gorm.git
Fix create duplicated value when updating nested has many relationship, close #4796
This commit is contained in:
parent
45e804dd3f
commit
27e2753c9d
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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] })
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue