Existuje samozřejmě několik přístupů v závislosti na dostupné verzi MongoDB. Ty se liší podle různých použití $lookup
až po povolení manipulace s objekty na .populate()
výsledek přes .lean()
.
Žádám vás, abyste si pečlivě přečetli sekce a uvědomte si, že při zvažování vašeho implementačního řešení nemusí být vše tak, jak se zdá.
MongoDB 3.6, "vnořený" $lookup
S MongoDB 3.6 $lookup
operátor získá další možnost zahrnout pipeline
výraz na rozdíl od jednoduchého spojení „místní“ k „cizí“ hodnotě klíče, to znamená, že můžete v podstatě provést každý $lookup
jako „vnořené“ do těchto výrazů kanálu
Venue.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(id.id) } },
{ "$lookup": {
"from": Review.collection.name,
"let": { "reviews": "$reviews" },
"pipeline": [
{ "$match": { "$expr": { "$in": [ "$_id", "$$reviews" ] } } },
{ "$lookup": {
"from": Comment.collection.name,
"let": { "comments": "$comments" },
"pipeline": [
{ "$match": { "$expr": { "$in": [ "$_id", "$$comments" ] } } },
{ "$lookup": {
"from": Author.collection.name,
"let": { "author": "$author" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$author" ] } } },
{ "$addFields": {
"isFollower": {
"$in": [
mongoose.Types.ObjectId(req.user.id),
"$followers"
]
}
}}
],
"as": "author"
}},
{ "$addFields": {
"author": { "$arrayElemAt": [ "$author", 0 ] }
}}
],
"as": "comments"
}},
{ "$sort": { "createdAt": -1 } }
],
"as": "reviews"
}},
])
To může být opravdu docela silné, jak vidíte z pohledu původního kanálu, ve skutečnosti ví pouze o přidávání obsahu do "reviews"
pole a pak každý následující „vnořený“ výraz potrubí také vždy vidí své „vnitřní“ prvky ze spojení.
Je výkonný a v některých ohledech může být o něco jasnější, protože všechny cesty pole jsou relativní k úrovni vnoření, ale začíná to odsazení ve struktuře BSON a musíte si být vědomi toho, zda odpovídáte polím. nebo singulárních hodnot při procházení strukturou.
Všimněte si, že zde můžeme také dělat věci, jako je "zploštění vlastnosti autora", jak je vidět v "comments"
položky pole. Vše $lookup
cílovým výstupem může být „pole“, ale v rámci „sub-pipeline“ můžeme toto pole jednotlivých prvků přetvořit na jedinou hodnotu.
Standardní MongoDB $lookup
Stále si ponecháte "připojit se k serveru" a můžete to udělat pomocí $lookup
, ale vyžaduje to pouze střední zpracování. Toto je dlouhodobý přístup s dekonstrukcí pole pomocí $unwind
a pomocí $group
fáze k přestavbě polí:
Venue.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(id.id) } },
{ "$lookup": {
"from": Review.collection.name,
"localField": "reviews",
"foreignField": "_id",
"as": "reviews"
}},
{ "$unwind": "$reviews" },
{ "$lookup": {
"from": Comment.collection.name,
"localField": "reviews.comments",
"foreignField": "_id",
"as": "reviews.comments",
}},
{ "$unwind": "$reviews.comments" },
{ "$lookup": {
"from": Author.collection.name,
"localField": "reviews.comments.author",
"foreignField": "_id",
"as": "reviews.comments.author"
}},
{ "$unwind": "$reviews.comments.author" },
{ "$addFields": {
"reviews.comments.author.isFollower": {
"$in": [
mongoose.Types.ObjectId(req.user.id),
"$reviews.comments.author.followers"
]
}
}},
{ "$group": {
"_id": {
"_id": "$_id",
"reviewId": "$review._id"
},
"name": { "$first": "$name" },
"addedBy": { "$first": "$addedBy" },
"review": {
"$first": {
"_id": "$review._id",
"createdAt": "$review.createdAt",
"venue": "$review.venue",
"author": "$review.author",
"content": "$review.content"
}
},
"comments": { "$push": "$reviews.comments" }
}},
{ "$sort": { "_id._id": 1, "review.createdAt": -1 } },
{ "$group": {
"_id": "$_id._id",
"name": { "$first": "$name" },
"addedBy": { "$first": "$addedBy" },
"reviews": {
"$push": {
"_id": "$review._id",
"venue": "$review.venue",
"author": "$review.author",
"content": "$review.content",
"comments": "$comments"
}
}
}}
])
To opravdu není tak skličující, jak byste si mohli zpočátku myslet, a řídí se jednoduchým vzorem $lookup
a $unwind
jak postupujete každým polem.
"author"
detail je samozřejmě ojedinělý, takže jakmile je „odmotán“, chcete to jednoduše nechat tak, provést přidání pole a zahájit proces „navrácení“ do polí.
Jsou pouze dva úrovně rekonstruovat zpět na původní Venue
dokumentu, takže první úroveň podrobností je podle Review
znovu vytvořit "comments"
pole. Vše, co potřebujete, je $push
cestu "$reviews.comments"
za účelem jejich shromažďování a pokud "$reviews._id"
pole je v "grouping _id", jediné další věci, které musíte zachovat, jsou všechna ostatní pole. To vše můžete vložit do _id
také, nebo můžete použít $first
.
Díky tomu existuje pouze jedna další $group
fázi, abyste se dostali zpět na Venue
sám. Tentokrát je seskupovací klíč "$_id"
samozřejmě se všemi vlastnostmi samotného místa konání pomocí $first
a zbývající "$review"
podrobnosti se vrátí do pole pomocí $push
. Samozřejmě "$comments"
výstup z předchozí $group
se změní na "review.comments"
cesta.
Pracovat na jediném dokumentu a jeho souvislostech to opravdu není tak špatné. $unwind
provozovatel potrubí může obecně být problémem s výkonem, ale v kontextu tohoto použití by to ve skutečnosti nemělo mít takový dopad.
Protože se data stále "připojují na server", stále existují mnohem menší provoz než druhá zbývající alternativa.
Manipulace s JavaScriptem
Samozřejmě dalším případem je, že místo změny dat na samotném serveru ve skutečnosti manipulujete s výsledkem. Ve většině Případy bych byl pro tento přístup, protože jakékoli "doplnění" dat pravděpodobně nejlépe zvládne klient.
Problém samozřejmě s použitím populate()
je to, i když to může 'vypadat' mnohem jednodušší proces, ve skutečnosti to NENÍ PŘIPOJENÍ jakýmkoliv způsobem. Vše populate()
ve skutečnosti je "skrýt" základní proces odeslání vícenásobného dotazy do databáze a poté čekání na výsledky prostřednictvím asynchronního zpracování.
Tedy "vzhled" spojení je ve skutečnosti výsledkem několika požadavků na server a následného provedení "manipulace na straně klienta" dat pro vložení podrobností do polí.
Takže kromě toho jasného varování že výkonnostní charakteristiky nejsou zdaleka na stejné úrovni jako server $lookup
, další výhradou je samozřejmě to, že „mongoose Documents“ ve výsledku nejsou ve skutečnosti obyčejné objekty JavaScriptu, které by bylo možné dále upravovat.
Chcete-li tedy použít tento přístup, musíte přidat .lean()
metodu na dotaz před spuštěním, aby dal mongoose pokyn vrátit „prosté objekty JavaScriptu“ namísto Document
typy, které jsou přetypovány pomocí metod schémat připojených k modelu. Samozřejmě je třeba poznamenat, že výsledná data již nemají přístup k žádným „metodám instancí“, které by jinak byly spojeny se samotnými souvisejícími modely:
let venue = await Venue.findOne({ _id: id.id })
.populate({
path: 'reviews',
options: { sort: { createdAt: -1 } },
populate: [
{ path: 'comments', populate: [{ path: 'author' }] }
]
})
.lean();
Nyní venue
je prostý objekt, můžeme jednoduše zpracovat a upravit podle potřeby:
venue.reviews = venue.reviews.map( r =>
({
...r,
comments: r.comments.map( c =>
({
...c,
author: {
...c.author,
isAuthor: c.author.followers.map( f => f.toString() ).indexOf(req.user.id) != -1
}
})
)
})
);
Takže je to opravdu jen otázka procházení každým z vnitřních polí dolů až na úroveň, kde můžete vidět followers
pole v rámci author
podrobnosti. Porovnání pak může být provedeno s ObjectId
hodnoty uložené v tomto poli po prvním použití .map()
vrátit hodnoty "řetězec" pro porovnání s req.user.id
což je také řetězec (pokud tomu tak není, přidejte také .toString()
on that ), protože je obecně jednodušší tyto hodnoty porovnávat tímto způsobem pomocí kódu JavaScript.
Znovu, i když musím zdůraznit, že to „vypadá jednoduše“, ale ve skutečnosti je to věc, které se opravdu chcete vyhnout kvůli výkonu systému, protože tyto dodatečné dotazy a přenos mezi serverem a klientem stojí hodně času na zpracování. a to i vzhledem k režii požadavku načítá skutečné náklady na dopravu mezi poskytovateli hostingu.
Shrnutí
To jsou v zásadě vaše přístupy, které můžete použít, kromě „rolování vlastních“, kdy ve skutečnosti provádíte „vícenásobné dotazy“ do databáze sami namísto použití pomocníka .populate()
je.
Pomocí vyplnění výstupu pak můžete jednoduše manipulovat s daty ve výsledku stejně jako s jakoukoli jinou datovou strukturou, pokud použijete .lean()
na dotaz převést nebo jinak extrahovat data prostého objektu z vrácených dokumentů mongoose.
I když se zdá, že agregované přístupy jsou mnohem složitější, je jich "hodně" další výhody této práce na serveru. Větší sady výsledků lze třídit, lze provádět výpočty pro další filtrování a samozřejmě získáte "jedinou odpověď" na "jediný požadavek" na server, to vše bez další režie.
Je zcela diskutabilní, že samotné kanály by mohly být jednoduše konstruovány na základě atributů již uložených ve schématu. Takže napsat vlastní metodu k provedení této "konstrukce" na základě připojeného schématu by nemělo být příliš obtížné.
Z dlouhodobějšího hlediska samozřejmě $lookup
je lepší řešení, ale pravděpodobně si budete muset dát trochu více práce s počátečním kódováním, pokud samozřejmě nebudete jen jednoduše kopírovat z toho, co je zde uvedeno;)