MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Mgo-Aggregation:Wie können Modelltypen wiederverwendet werden, um gemischte Ergebnisse abzufragen und zu entpacken?

Die obige Abfrage gibt Dokumente zurück, die "fast" mit User übereinstimmen Dokumente, aber sie haben auch die Beiträge der einzelnen Benutzer. Das Ergebnis ist also im Grunde eine Reihe von User Dokumente mit einem Post Array oder Slice eingebettet .

Eine Möglichkeit wäre, einen Posts []*Post hinzuzufügen Feld an den User selbst, und wir wären fertig:

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

Während dies funktioniert, scheint es "übertrieben" zu sein, User zu erweitern mit Posts nur wegen einer einzigen Abfrage. Wenn wir diesen Weg fortsetzen würden, unser User type würde mit vielen "zusätzlichen" Feldern für verschiedene Abfragen aufgebläht. Ganz zu schweigen davon, ob wir die Posts füllen eingeben und den Benutzer speichern, würden diese Beiträge im User gespeichert dokumentieren. Nicht das, was wir wollen.

Eine andere Möglichkeit wäre, einen UserWithPosts zu erstellen geben Sie Kopieren von User ein und Hinzufügen eines Posts []*Post aufstellen. Unnötig zu erwähnen, dass dies hässlich und unflexibel ist (jede Änderung, die an User müsste in UserWithPosts widergespiegelt werden manuell).

Mit Struktureinbettung

Anstatt den ursprünglichen User zu ändern , und anstatt einen neuen UserWithPosts zu erstellen Typ von "scratch", wir können struct embedding verwenden (Wiederverwendung des vorhandenen User und Post Typen) mit einem kleinen Trick:

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

Beachten Sie den Tag-Wert bson ",inline" . Dies ist dokumentiert unter bson.Marshal() und bson.Unmarshal() (Wir verwenden es zum Unmarshaling):

Durch die Verwendung von Embedding und dem ",inline" Tag-Wert, der UserWithPosts type selbst wird ein gültiges Ziel für das Unmarshaling von User sein Dokumente und deren Post []*Post Feld ist eine perfekte Wahl für die nachgeschlagenen "posts" .

Verwendung:

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

Oder Sie erhalten alle Ergebnisse in einem Schritt:

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

Die Typdeklaration von UserWithPosts kann eine lokale Deklaration sein oder nicht. Wenn Sie es an keiner anderen Stelle benötigen, kann es sich um eine lokale Deklaration in der Funktion handeln, in der Sie die Aggregationsabfrage ausführen und verarbeiten, damit Ihre vorhandenen Typen und Deklarationen nicht aufgebläht werden. Wenn Sie es wiederverwenden möchten, können Sie es auf Paketebene deklarieren (exportiert oder nicht exportiert) und verwenden, wo immer Sie es brauchen.

Ändern der Aggregation

Eine weitere Option ist die Verwendung von $replaceRoot von MongoDB um die Ergebnisdokumente "umzuordnen", sodass eine "einfache" Struktur die Dokumente perfekt abdeckt:

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

Mit dieser Neuzuordnung können die Ergebnisdokumente wie folgt modelliert werden:

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

Beachten Sie, dass, während dies funktioniert, die posts Feld aller Dokumente wird zweimal vom Server geholt:einmal als posts Feld der zurückgegebenen Dokumente und einmal als Feld von user; wir mappen/verwenden es nicht, aber es ist in den Ergebnisdokumenten vorhanden. Wird also diese Lösung gewählt, wird die user.posts Feld sollte entfernt werden, z. mit einem $project Stufe:

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