我必须将多类型结构保留在slice中并为它们播种。我采用了接口类型的可变参数,并对它们进行了预处理。如果我调用接口的方法,它可以工作,但当我试图访问结构时,我不能。我该如何解决?
注意:Seed()方法返回数据的文件名。
接口:
type Seeder interface {
Seed() string
}
方法:
func (AirportCodes) Seed() string {
return "airport_codes.json"
}
SeederSlice:
seederModelList = []globals.Seeder{
m.AirportCodes{},
m.Term{},
}
最后一个,SeedSchema函数:
func (db *Database) SeedSchema(models ...globals.Seeder) error {
var (
subjects []globals.Seeder
fileByte []byte
err error
// tempMember map[string]interface{}
)
if len(models) == 0 {
subjects = seederModelList
} else {
subjects = models
}
for _, model := range subjects {
fileName := model.Seed()
fmt.Printf("%+vn", model)
if fileByte, err = os.ReadFile("db/seeds/" + fileName); err != nil {
fmt.Println("asd", err)
// return err
}
if err = json.Unmarshal(fileByte, &model); err != nil {
fmt.Println("dsa", err)
// return err
}
modelType := reflect.TypeOf(model).Elem()
modelPtr2 := reflect.New(modelType)
fmt.Printf("%sn", modelPtr2)
}
return nil
}
我可以达到确切的模型,但无法创建成员和种子。
在评论中反复讨论之后,我将在这里发布这个最简单的答案。这绝不是一个决定性的"这就是你所做的">键入答案,但我希望这至少能为你提供足够的信息,让你开始。为了达到这一点,我已经根据您提供的代码片段做出了一些假设,并且我假设您希望通过排序命令(例如your_bin seed
)为DB种子。这意味着已经做出了以下假设:
- 存在Schemas和相应的模型/类型(如
AirportCodes
等) - 每个类型都有自己的源文件(名称来自
Seed()
方法,返回一个.json
文件名) - 因此,假设种子数据的格式类似于
[{"seed": "data"}, {"more": "data"}]
- 种子文件可以被附加,如果模式发生变化,种子文件中的数据可以一起发生变化。这在ATM上并不重要,但这仍然是一个需要注意的假设
好的,让我们从将所有JSON文件移动到可预测的位置开始。在一个相当大的、真实世界的应用程序中,您会使用类似XDG基本路径的东西,但为了简洁起见,让我们假设您在/
的一个临时容器中运行它,并且所有相关资产都已复制到所述容器中。
将所有种子文件都放在seed_data
目录下的基本路径中是有意义的。每个文件都包含特定表的种子数据,因此文件中的所有数据都整齐地映射到一个模型上。让我们暂时忽略关系数据。目前,我们只假设这些文件中的数据至少在内部是一致的,并且任何X-to-X
关系数据都必须具有允许JOIN等的正确ID字段。
开始吧
所以我们有了我们的模型和JSON文件中的数据。现在,我们只需创建所述模型的一部分,确保在插入其他数据之前,您希望/需要存在的数据被表示为比其他数据更高的条目(更低的索引)。有点像这样:
seederModelList = []globals.Seeder{
m.AirportCodes{}, // seeds before Term
m.Term{}, // seeds after AirportCodes
}
但是,或者从这个Seed
方法返回文件名,为什么不传入连接并让模型处理自己的数据,如下所示:
func (_ AirportCodes) Seed(db *gorm.DB) error {
// we know what file this model uses
data, err := os.ReadFile("seed_data/airport_codes.json")
if err != nil {
return err
}
// we have the data, we can unmarshal it as AirportCode instances
codes := []*AirportCodes{}
if err := json.Unmarshal(data, &codes); err != nil {
return err
}
// now INSERT, UPDATE, or UPSERT:
db.Clauses(clause.OnConflict{
UpdateAll: true,
}).Create(&codes)
}
对其他型号也这样做,如Terms
:
func (_ Terms) Seed(db *gorm.DB) error {
// we know what file this model uses
data, err := os.ReadFile("seed_data/terms.json")
if err != nil {
return err
}
// we have the data, we can unmarshal it as Terms instances
terms := []*Terms{}
if err := json.Unmarshal(data, &terms); err != nil {
return err
}
// now INSERT, UPDATE, or UPSERT:
return db.Clauses(clause.OnConflict{
UpdateAll: true,
}).Create(&terms)
}
当然,考虑到我们在模型中有数据库访问,这确实会导致一些混乱,如果你问我的话,这应该只是一个DTO。这在错误处理方面也有很多不足之处,但它的基本要点是:
func main() {
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) // omitted error handling for brevity
seeds := []interface{
Seed(*gorm.DB) error
}{
model.AirportCodes{},
model.Terms{},
// etc...
}
for _, m := range seeds {
if err := m.Seed(db); err != nil {
panic(err)
}
}
db.Close()
}
好吧,这应该让我们开始,但让我们通过将这一切转移到更好的地方
- 将整个DB交互移出DTO/模型
- 将事情打包到事务中,这样我们就可以在出错时回滚
- 将初始切片更新一点,使其更干净
如前所述,我假设您有类似存储库的东西,可以在一个单独的包中处理DB交互。我们不应该在模型上调用Seed
,并将DB连接传递到这些数据库中,而应该依赖于我们的存储库:
db, _ := gorm.Open() // same as before
acs := repo.NewAirportCodes(db) // pass in connection
tms := repo.NewTerms(db) // again...
现在,我们的模型仍然可以返回JSON文件名,或者我们可以在repos中将其作为const
。在这一点上,这并不重要。最重要的是,我们可以在存储库中实际插入数据。
如果你愿意,你可以把你的种子切片改成这样:
calls := []func() error{
acs.Seed, // assuming your repo has a Seed function that does what it's supposed to do
tms.Seed,
}
然后在一个循环中执行所有播种:
for _, c := range calls {
if err := c(); err != nil {
panic(err)
}
}
现在,我们只剩下交易的问题了。值得庆幸的是,gorm让这件事变得非常简单:
db, _ := gorm.Open()
db.Transaction(func(tx *gorm.DB) error {
acs := repo.NewAirportCodes(tx) // create repo's, but use TX for connection
if err := acs.Seed(); err != nil {
return err // returning an error will automatically rollback the transaction
}
tms := repo.NewTerms(tx)
if err := tms.Seed(); err != nil {
return err
}
return nil // commit transaction
})
这里还有很多你可以处理的事情,比如创建可以单独提交的一批相关数据,你可以添加更精确的错误处理和信息更丰富的日志记录,更好地处理冲突(区分CREATE和UPDATE等)
Gorm有一个迁移系统
我必须承认,我已经有很长一段时间没有处理gorm了,但IIRC,如果模型发生变化,你可以让表自动迁移,并在启动时运行自定义go代码和/或SQL文件,这些文件可以很容易地用于数据种子。也许值得一看这样做的可行性。。。