使用切片的动态结构(包含外键)和 go-gorm 将数据插入数据库

Using dynamic struct of slices (which contains foreign keys) and go-gorm to Insert data into db

我有一个从 Ompluscator 创建的动态结构,它包含一个带有外键引用的嵌套结构( 结构 的切片)。

父项 (User Table) 和子项 (Credit Card) Table 已成功创建。即使插入用户 Table 也是成功的,但后来在处理 credit_card table 条目时以 field value not valid 错误结束。

完整代码如下:

creditCardStruct := dynamicstruct.NewStruct().
    AddField("Bank", "", `protobuf:"bytes,1,opt,name=number,proto3" json:"bank,omitempty" gorm:"column:bank;not null"`).
    AddField("Number", "", `protobuf:"bytes,1,opt,name=number,proto3" json:"number,omitempty" gorm:"column:number;not null;size:16;primary_key"`).
    AddField("Cvv", "", `protobuf:"bytes,2,opt,name=cvv,proto3" json:"cvv,omitempty" gorm:"column:cvv;not null;size:5" skyflow:"only4digits"`).
    //AddField("Expiry", timeNow, `protobuf:"bytes,3,opt,name=expiry,proto3" json:"expiry,omitempty" gorm:"column:expiry;type:timestamp;not null;"`).
    AddField("Name", "", `protobuf:"bytes,1,opt,name=number,proto3" json:"name,omitempty" gorm:"column:name;not null"`).
    AddField("ProfileID", "", `protobuf:"bytes,5,opt,name=userName,proto3" json:"profile_id,omitempty" sql:"type:string REFERENCES user_normalized1(profile_id)" gorm:"column:profile_id;not null;primary_key"`).
    Build().NewSliceOfStructs()

userInstance := dynamicstruct.NewStruct().
    AddField("ProfileID", "", `protobuf:"bytes,1,opt,name=number,proto3" json:"profile_id,omitempty" gorm:"column:profile_id;unique;not null;unique_index;primary_key"`).
    AddField("CreditCards", creditCardStruct, `protobuf:"bytes,1,opt,name=number,proto3" json:"credit_cards,omitempty" gorm:"foreignkey:ProfileID;association_foreignkey:ProfileID"`).
    Build().
    New()

db.Table("user_normalized1").AutoMigrate(userInstance)
db.Table("credit_card1").AutoMigrate(creditCardStruct)

data := []byte(`
            {
            "profile_id":"123",
            "first_name": "fname",
            "last_name": "lname",
            "some_text": "dummy"

            ,"credit_cards": [{"bank":"bank1", "cvv":"123", "name":"fname", "number":"nlabla1"},{"bank":"bank2", "cvv":"1234", "name":"lname", "number":"nlabla2"}]
            }
            `)

err := json.Unmarshal(data, &userInstance)
if err != nil {
    log.Fatal(err)
}
data, err = json.Marshal(userInstance)
if err != nil {
    log.Fatal(err)
}

fmt.Println(string(data))

db.Table("user_normalized1").Create(userInstance)

这是日志:

[2020-06-04 16:03:25]  [8.93ms]  CREATE TABLE "user_normalized1" ("profile_id" text NOT NULL UNIQUE , PRIMARY KEY ("profile_id"))  
[0 rows affected or returned ] 

[2020-06-04 16:03:25]  [106.26ms]  CREATE UNIQUE INDEX uix_user_normalized1_profile_id ON "user_normalized1"(profile_id)   
[0 rows affected or returned ] 

[2020-06-04 16:03:25]  [44.12ms]  CREATE TABLE "credit_card1" ("profile_id" string REFERENCES user_normalized1(profile_id) NOT NULL,"bank" text NOT NULL,"number" varchar(16) NOT NULL,"cvv" varchar(5) NOT NULL,"name" text NOT NULL , PRIMARY KEY ("profile_id","number"))  
[0 rows affected or returned ] 
{"profile_id":"123","credit_cards":[{"bank":"bank1","number":"nlabla1","cvv":"123","name":"fname"},{"bank":"bank2","number":"nlabla2","cvv":"1234","name":"lname"}]}
[2020-06-04 16:03:25]  [1.50ms]  INSERT INTO "user_normalized1" ("profile_id") VALUES ('123') RETURNING "user_normalized1"."profile_id"  
[1 rows affected or returned ] 
[2020-06-04 16:03:25]  field value not valid 

我找不到有关如何实现它的任何信息。经过大量调试后我能够让它工作,所以发布我的解决方案。

简答:

嵌套切片必须是指针数组

以这种方式定义您的 Nested/Child 结构

 creditCardStruct := dynamicstruct.NewStruct().
        AddField("Bank", "", `protobuf:"bytes,1,opt,name=number,proto3" json:"bank,omitempty" gorm:"column:bank;not null"`).
        AddField("Number", "", `protobuf:"bytes,1,opt,name=number,proto3" json:"number,omitempty" gorm:"column:number;not null;size:16;primary_key"`).
        AddField("Cvv", "", `protobuf:"bytes,2,opt,name=cvv,proto3" json:"cvv,omitempty" gorm:"column:cvv;not null;size:5" skyflow:"only4digits"`).
        //AddField("Expiry", timeNow, `protobuf:"bytes,3,opt,name=expiry,proto3" json:"expiry,omitempty" gorm:"column:expiry;type:timestamp;not null;"`).
        AddField("Name", "", `protobuf:"bytes,1,opt,name=number,proto3" json:"name,omitempty" gorm:"column:name;not null"`).
        AddField("ProfileID", "", `protobuf:"bytes,5,opt,name=userName,proto3" json:"profile_id,omitempty" sql:"type:string REFERENCES user_normalized1(profile_id)" gorm:"column:profile_id;not null;primary_key"`).
        Build().New()

 creditCardArrayInstance := reflect.New(reflect.SliceOf(reflect.TypeOf(creditCardStruct))).Elem().Interface()

 userInstance := dynamicstruct.NewStruct().
    AddField("ProfileID", "", `protobuf:"bytes,1,opt,name=number,proto3" json:"profile_id,omitempty" gorm:"column:profile_id;unique;not null;unique_index;primary_key"`).
    AddField("CreditCards", creditCardArrayInstance, `protobuf:"bytes,1,opt,name=number,proto3" json:"credit_cards,omitempty" gorm:"foreignkey:ProfileID;association_foreignkey:ProfileID"`).
    Build().
    New()

db.Table("user_normalized1").AutoMigrate(userInstance)
db.Table("credit_card1").AutoMigrate(creditCardStruct)

长答案:

  1. 当 DynamicStruct 创建 Slice of Struct 时,它正在创建切片的指针 reflect.New(reflect.SliceOf(ds.definition)).Interface()。 这实际上是一个 指向 Slice 的指针。所以 []*CreditCards 变成了 *[]*CreditCards
  2. 当 gorm 执行一个 callback to save Associations 时,它有一个单独的块来处理切片,识别的运行时类型不是切片(它是指针)。所以它假定它是结构体,并继续将它作为一个简单的结构体处理。
  3. 然而,当 gorm 为非结构字段(如切片)构建 fields from scope 时,它不会设置字段定义。因此,当 gorm 将前一个切片作为普通结构处理时,它的字段是无效类型,因为它从未被设置。

所以解决方案是手动创建动态结构作为指针数组,而不是提供的 NewSliceOfStructs API.