如何达到接口类型的结构成员

我必须在切片中保留多类型结构并为它们播种。我接受了接口类型的可变参数并 foreach 它们。如果我调用接口的方法,它就可以工作,但是当我试图访问结构时,我不能。我该如何解决?


注意:Seed() 方法返回数据的文件名。


界面:


type Seeder interface {

    Seed() string

}

方法:


func (AirportCodes) Seed() string {

    return "airport_codes.json"

}

播种机切片:


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("%+v\n", 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("%s\n", modelPtr2) 

    }

    return nil

}

我可以达到精确的模型,但无法创建成员和种子。


侃侃尔雅
浏览 92回答 1
1回答

开满天机

在评论中反复讨论之后,我将在此处发布这个最小的答案。这绝不是一个明确的“这就是你所做的”类型的答案,但我希望这至少可以为你提供足够的信息来帮助你开始。为了达到这一点,我根据您提供的代码片段做出了一些假设,并且我假设您希望通过某种命令(例如)为数据库播种your_bin seed。这意味着做出了以下假设:存在模式和相应的模型/类型(类似AirportCodes等)每种类型都有自己的源文件(名称来自Seed()方法,返回一个.json文件名)因此,假定种子数据的格式类似于[{"seed": "data"}, {"more": "data"}].可以附加种子文件,如果模式发生变化,种子文件中的数据也可以一起更改。这在 ATM 中不太重要,但仍然是一个应该注意的假设。好的,让我们首先将所有 JSON 文件移动到可预测的位置。在一个相当大的真实世界应用程序中,您会使用类似XDG 基本路径的东西,但为了简洁起见,我们假设您在一个临时容器中运行它,并且/所有相关资产都已复制到所述容器中。将所有种子文件放在seed_data目录下的基本路径中是有意义的。每个文件都包含特定表的种子数据,因此一个文件中的所有数据都整齐地映射到一个模型上。让我们暂时忽略关系数据。我们只是假设,现在,这些文件中的数据至少在内部是一致的,并且任何X-to-X关系数据都必须具有正确的 ID 字段,允许 JOIN 等。开始吧所以我们有了我们的模型,以及 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()}好的,这应该让我们开始了,但让我们通过以下方式将这一切变成更好的东西:将整个数据库交互移出 DTO/模型将事情包装到事务中,这样我们就可以回滚错误稍微更新初始切片以使事情更清晰因此,如前所述,我假设您有存储库之类的东西来处理单独包中的数据库交互。我们不应该调用Seed模型并将数据库连接传递给模型,而应该依赖我们的存储库:db, _ := gorm.Open() // same as beforeacs := repo.NewAirportCodes(db) // pass in connectiontms := repo.NewTerms(db) // again...现在我们的模型仍然可以返回 JSON 文件名,或者我们可以将其作为constrepos 中的一个。在这一点上,这并不重要。最主要的是,我们可以在存储库中完成实际的数据插入。如果你愿意,你可以将你的种子切片更改为这样的东西: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 文件,这些都可以使用,相当容易地播种数据。可能值得研究一下它的可行性……
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go