为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
侃侃尔雅 2023-03-07 11:07:15
我必须在切片中保留多类型结构并为它们播种。我接受了接口类型的可变参数并 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}我可以达到精确的模型,但无法创建成员和种子。
查看完整描述

1 回答

?
开满天机

TA贡献1786条经验 获得超13个赞

在评论中反复讨论之后,我将在此处发布这个最小的答案。这绝不是一个明确的“这就是你所做的”类型的答案,但我希望这至少可以为你提供足够的信息来帮助你开始。为了达到这一点,我根据您提供的代码片段做出了一些假设,并且我假设您希望通过某种命令(例如)为数据库播种your_bin seed。这意味着做出了以下假设:

  1. 存在模式和相应的模型/类型(类似AirportCodes等)

  2. 每种类型都有自己的源文件(名称来自Seed()方法,返回一个.json文件名)

  3. 因此,假定种子数据的格式类似于[{"seed": "data"}, {"more": "data"}].

  4. 可以附加种子文件,如果模式发生变化,种子文件中的数据也可以一起更改。这在 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()

}

好的,这应该让我们开始了,但让我们通过以下方式将这一切变成更好的东西:

  1. 将整个数据库交互移出 DTO/模型

  2. 将事情包装到事务中,这样我们就可以回滚错误

  3. 稍微更新初始切片以使事情更清晰

因此,如前所述,我假设您有存储库之类的东西来处理单独包中的数据库交互。我们不应该调用Seed模型并将数据库连接传递给模型,而应该依赖我们的存储库:

db, _ := gorm.Open() // same as before

acs := repo.NewAirportCodes(db) // pass in connection

tms := 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 文件,这些都可以使用,相当容易地播种数据。可能值得研究一下它的可行性……


查看完整回答
反对 回复 2023-03-07
  • 1 回答
  • 0 关注
  • 90 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信