MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Aggregazione Mgo:come riutilizzare i tipi di modello per interrogare e annullare il marshalling di risultati misti?

La query precedente restituisce documenti che "quasi" corrispondono a User documenti, ma hanno anche i post di ogni utente. Quindi sostanzialmente il risultato è una serie di User documenti con un Posts array o slice embedded .

Un modo sarebbe aggiungere un Posts []*Post campo all'User stesso, e avremmo finito:

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

Anche se funziona, sembra "eccessivo" estendere User con Posts solo per il bene di una singola query. Se dovessimo continuare su questa strada, il nostro User il tipo si riempirebbe di molti campi "extra" per query diverse. Per non parlare se riempiamo i Posts campo e salva l'utente, quei post finirebbero per essere salvati all'interno di User documento. Non quello che vogliamo.

Un altro modo sarebbe creare un UserWithPosts digita copiando User e aggiungendo un Posts []*Post campo. Inutile dire che questo è brutto e inflessibile (qualsiasi modifica apportata a User dovrebbe riflettersi in UserWithPosts manualmente).

Con struct Embedding

Invece di modificare l'User originale e invece di creare un nuovo UserWithPosts digitare da "scratch", possiamo utilizzare incorporamento di strutture (riutilizzando l'User esistente e Post tipi) con un piccolo trucco:

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

Nota il valore del tag bson ",inline" . Ciò è documentato in bson.Marshal() e bson.Unmarshal() (lo useremo per l'annullamento del marshalling):

Usando l'incorporamento e il ",inline" valore del tag, il UserWithPosts il tipo stesso sarà un obiettivo valido per l'annullamento del marshalling di User documenti e il relativo Posts []*Post campo sarà una scelta perfetta per i "posts" cercati .

Usandolo:

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

O ottenere tutti i risultati in un solo passaggio:

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

La dichiarazione del tipo di UserWithPosts può essere o meno una dichiarazione locale. Se non ne hai bisogno altrove, può essere una dichiarazione locale nella funzione in cui esegui ed elabori la query di aggregazione, quindi non gonfierà i tuoi tipi e dichiarazioni esistenti. Se vuoi riutilizzarlo, puoi dichiararlo a livello di pacchetto (esportato o non esportato) e usarlo dove ti serve.

Modifica dell'aggregazione

Un'altra opzione è utilizzare $replaceRoot di MongoDB. per "riordinare" i documenti risultanti, in modo che una struttura "semplice" copra perfettamente i documenti:

// 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",
            },
        },
    },
})

Con questa rimappatura, i documenti dei risultati possono essere modellati in questo modo:

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

Nota che mentre funziona, i posts campo di tutti i documenti verrà prelevato dal server due volte:una volta come posts campo dei documenti restituiti e una volta come campo di user; non lo mappiamo / lo usiamo ma è presente nei documenti dei risultati. Quindi, se viene scelta questa soluzione, user.posts il campo deve essere rimosso ad es. con un $project fase:

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}},
})