MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Agregação Mgo:como reutilizar tipos de modelo para consultar e desempacotar resultados mistos?


A consulta acima retorna documentos que "quase" correspondem a User documentos, mas também têm as postagens de cada usuário. Então, basicamente, o resultado é uma série de User documentos com um Post array ou slice incorporado .

Uma maneira seria adicionar um Posts []*Post campo para o User em si, e teríamos feito:
type User struct {
    ID         string    `bson:"_id"`
    Name       string    `bson:"name"`
    Registered time.Time `bson:"registered"`
    Posts      []*Post   `bson:"posts,omitempty"`
}

Embora isso funcione, parece "exagero" estender User com Posts apenas por causa de uma única consulta. Se continuarmos por esse caminho, nosso User type ficaria inchado com muitos campos "extras" para diferentes consultas. Sem contar se preenchermos os Posts campo e salvar o usuário, essas postagens acabariam salvas dentro do User documento. Não é o que queremos.

Outra maneira seria criar um UserWithPosts digite copiando User , e adicionando um Posts []*Post campo. Desnecessário dizer que isso é feio e inflexível (qualquer alteração feita em User teria que ser refletido para UserWithPosts manualmente).

Com incorporação de estrutura


Em vez de modificar o User original , e em vez de criar um novo UserWithPosts type do "scratch", podemos utilizar incorporação de struct (reutilizando o User existente e Post tipos) com um pequeno truque:
type UserWithPosts struct {
    User  `bson:",inline"`
    Posts []*Post `bson:"posts"`
}

Observe o valor da tag bson ",inline" . Isso está documentado em bson.Marshal() e bson.Unmarshal() (vamos usá-lo para unmarshaling):

Usando a incorporação e o ",inline" valor da tag, o UserWithPosts o próprio tipo será um destino válido para unmarshaling de User documentos e seu Post []*Post será uma escolha perfeita para os "posts" pesquisados .

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

Ou obtendo todos os resultados em uma única etapa:
var uwps []*UserWithPosts
err := pipe.All(&uwps)
// Handle error

A declaração de tipo de UserWithPosts pode ou não ser uma declaração local. Se você não precisar dele em outro lugar, pode ser uma declaração local na função em que você executa e processa a consulta de agregação, para que não aumente seus tipos e declarações existentes. Se você quiser reutilizá-lo, você pode declará-lo no nível do pacote (exportado ou não exportado) e usá-lo sempre que precisar.

Modificando a agregação


Outra opção é usar o $replaceRoot do MongoDB para "reorganizar" os documentos de resultado, para que uma estrutura "simples" cubra perfeitamente os documentos:
// 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",
            },
        },
    },
})

Com este remapeamento, os documentos resultantes podem ser modelados assim:
type UserWithPosts struct {
    User  *User   `bson:"user"`
    Posts []*Post `bson:"posts"`
}

Observe que enquanto isso funciona, os posts campo de todos os documentos será obtido do servidor duas vezes:uma vez como posts campo dos documentos retornados, e uma vez como o campo de user; não mapeamos/usamos, mas está presente nos documentos de resultado. Portanto, se esta solução for escolhida, o user.posts campo deve ser removido, por exemplo com um $project palco:
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}},
})