MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Agrégation Mgo :comment réutiliser les types de modèles pour interroger et démarshaler des résultats mitigés ?

La requête ci-dessus renvoie les documents qui correspondent "presque" à User documents, mais ils ont aussi les messages de chaque utilisateur. Donc, fondamentalement, le résultat est une série de User documents avec un Post tableau ou tranche intégré .

Une façon serait d'ajouter un Posts []*Post champ à l'User lui-même, et nous aurions fini :

type User struct {
    ID         string    `bson:"_id"`
    Name       string    `bson:"name"`
    Registered time.Time `bson:"registered"`
    Posts      []*Post   `bson:"posts,omitempty"`
}

Bien que cela fonctionne, il semble "exagéré" d'étendre User avec Posts juste pour le plaisir d'une seule requête. Si nous continuons dans cette voie, notre User le type serait gonflé avec beaucoup de champs "supplémentaires" pour différentes requêtes. Sans oublier si on remplit les Posts champ et enregistrez l'utilisateur, ces messages finiraient par être enregistrés dans le User document. Pas ce que nous voulons.

Une autre façon serait de créer un UserWithPosts tapez en copiant User , et en ajoutant un Posts []*Post champ. Inutile de dire que c'est moche et inflexible (toute modification apportée à User devrait être reflété dans UserWithPosts manuellement).

Avec l'intégration de structures

Au lieu de modifier le User d'origine , et au lieu de créer un nouveau UserWithPosts tapez à partir de "zéro", nous pouvons utiliser struct embedding (en réutilisant le User existant et Post types) avec une petite astuce :

type UserWithPosts struct {
    User  `bson:",inline"`
    Posts []*Post `bson:"posts"`
}

Notez la valeur de la balise bson ",inline" . Ceci est documenté sur bson.Marshal() et bson.Unmarshal() (nous l'utiliserons pour unmarshaling):

En utilisant l'incorporation et le ",inline" valeur de balise, le UserWithPosts le type lui-même sera une cible valide pour le désassemblage de User documents, et son Post []*Post sera un choix parfait pour les "posts" recherchés .

Utilisation :

var uwp *UserWithPosts
it := pipe.Iter()
for it.Next(&uwp) {
    // Use uwp:
    fmt.Println(uwp)
}
// Handle it.Err()

Ou obtenir tous les résultats en une seule étape :

var uwps []*UserWithPosts
err := pipe.All(&uwps)
// Handle error

La déclaration de type de UserWithPosts peut ou non être une déclaration locale. Si vous n'en avez pas besoin ailleurs, il peut s'agir d'une déclaration locale dans la fonction où vous exécutez et traitez la requête d'agrégation, afin de ne pas gonfler vos types et déclarations existants. Si vous souhaitez le réutiliser, vous pouvez le déclarer au niveau du package (exporté ou non exporté) et l'utiliser partout où vous en avez besoin.

Modifier l'agrégation

Une autre option consiste à utiliser $replaceRoot de MongoDB pour "réorganiser" les documents de résultat, ainsi une structure "simple" couvrira parfaitement les documents :

// Query users with their posts:
pipe := collUsers.Pipe([]bson.M{
    {
        "$lookup": bson.M{
            "from":         "posts",
            "localField":   "_id",
            "foreignField": "userID",
            "as":           "posts",
        },
    },
    {
        "$replaceRoot": bson.M{
            "newRoot": bson.M{
                "user":  "$$ROOT",
                "posts": "$posts",
            },
        },
    },
})

Avec ce remappage, les documents de résultat peuvent être modélisés comme ceci :

type UserWithPosts struct {
    User  *User   `bson:"user"`
    Posts []*Post `bson:"posts"`
}

Notez que pendant que cela fonctionne, les posts le champ de tous les documents sera extrait deux fois du serveur :une fois en tant que posts champ des documents retournés, et une fois comme champ de user; nous ne le mappons pas / ne l'utilisons pas mais il est présent dans les documents de résultat. Donc si cette solution est choisie, le user.posts champ doit être supprimé, par ex. avec un $project étape :

pipe := collUsers.Pipe([]bson.M{
    {
        "$lookup": bson.M{
            "from":         "posts",
            "localField":   "_id",
            "foreignField": "userID",
            "as":           "posts",
        },
    },
    {
        "$replaceRoot": bson.M{
            "newRoot": bson.M{
                "user":  "$$ROOT",
                "posts": "$posts",
            },
        },
    },
    {"$project": bson.M{"user.posts": 0}},
})