【问题标题】:Loop over collections in mongodb and decode to various go structs循环遍历 mongodb 中的集合并解码为各种 go 结构
【发布时间】:2021-09-01 05:05:36
【问题描述】:

我在 mongodb 实例中有一系列集合,我想在其中提取所有集合并解码集合中的所有文档,以便我可以随时改变它们。

我正在使用 go 列出集合名称:

...
collections, err := db.ListCollectionNames(context.TODO(), bson.M{})
if err != nil {
    log.Fatalf("Failed to get coll names: %v", err)
}

for _, coll := range collections {
    collection := db.Collection(coll)
...

这完美无缺,下一步是为每个集合打开一个游标并在游标上循环,并在每个对象上调用 decode 到其对应的类型。

我已经在一个类型包中定义了所有类型:

type Announcements struct {
    Id            string    `bson:"_id"`
    Content       string    `bson:"content"`
    Title         string    `bson:"title"`
    DateModified  time.Time `bson:"dateModified,omitempty"`
    ModifiedBy    string    `bson:"modifiedBy,omitempty"`
}

问题是我无法定义用于动态解码的变量。

需要为每个集合重复此代码

var obj Announcements // This would need to be dynamic

for cursor.Next(context.Background()) {
    err := cursor.Decode(&obj)
    if err != nil {
        log.Fatal(err)
    }

    log.Printf("%#v\n", obj)
}

有没有办法做到这一点而无需重复多次?我意识到动态类型的语言会更好。在我将工作迁移到脚本语言之前想问一下。

我尝试使用反射包和 switch 语句动态实例化变量并使用 interface{} 类型,但这会强制解码使用 bson 类型而不是我定义的结构。

-- 编辑--

我尝试使用映射将集合名称链接到类型,但无济于事。

var Collections = map[string]interface{}{
    "announcements":          Announcements{},
    "badges":                 Badges{},
    "betaUser":               BetaUser{},
    "bonusLearningItems":     BonusLearningItems{},
    "books":                  Books{},
...
}

因此,当我使用 obj var 时,我尝试了如下分配:

var obj types.Collections[coll]

希望如果我被循环到公告集合,这会给我一个公告类型的变量。但是当我调用 decode 它返回 bson 类型。

我需要动态定义obj变量类型。

【问题讨论】:

    标签: mongodb go


    【解决方案1】:

    有很多方法可以做到这一点。其中之一是使用回调函数:

    func Process(cursor *mongo.Cursor, cb func(), out interface{}) {
      for cursor.Next(context.Background()) {
         err := cursor.Decode(out)
         if err != nil {
             log.Fatal(err)
         }
         cb()
         log.Printf("%#v\n", obj)
      }
    }
    
    

    然后定义一个地图:

    type collHandler struct {
       New func() interface{}
       Process func(interface{})
    }
    
    
    var collections=map[string]func() interface{} {
       "announcements":   collHandler{
         New: func() interface {} {return &Announcement{}},
         Process: func(data interface{}) {
            processAnnouncements(data.(*Announcement))
         },
       },
       ...
    }
    

    然后你可以:

    handler:=collections[collectionName]
    obj:=handler.New()
    process(cursor,func() {
       handler.Process(obj)
      },
      &obj)
    
    

    这样,您可以将迭代放入一个通用函数中,并使用闭包来处理特定于类型的逻辑。

    【讨论】:

    • 谢谢你的回答我担心我的问题问得不够好。我需要一种将集合映射到要传递给 Process 函数的变量的方法。该 obj 变量需要根据集合的名称来定义。我会稍微更新一下我的问题。
    • 它很可怕,看起来很吓人,但它确实有效......哇。非常感谢。我会确保它适用于整个系列,然后我会给你一个大的绿色复选标记:)
    • “可怕”取决于您对语言的适应程度。这种模式其实很常见。
    • 我很抱歉,我应该说它不符合我对 Go 简单性的极限理解。无论如何,我都感谢您的回答
    猜你喜欢
    • 2015-07-27
    • 2019-02-03
    • 1970-01-01
    • 2013-05-06
    • 2019-02-18
    • 2018-06-16
    • 2016-07-16
    • 2014-08-13
    • 1970-01-01
    相关资源
    最近更新 更多