Het is niet nodig om de JSON te ontleden. Alles hier kan eigenlijk rechtstreeks worden gedaan met LINQ of de Aggregate Fluent-interfaces.
Gewoon wat demonstratielessen gebruiken omdat de vraag niet echt veel te bieden heeft.
Instellen
In principe hebben we hier twee collecties, namelijk
entiteiten
{ "_id" : ObjectId("5b08ceb40a8a7614c70a5710"), "name" : "A" }
{ "_id" : ObjectId("5b08ceb40a8a7614c70a5711"), "name" : "B" }
en anderen
{
"_id" : ObjectId("5b08cef10a8a7614c70a5712"),
"entity" : ObjectId("5b08ceb40a8a7614c70a5710"),
"name" : "Sub-A"
}
{
"_id" : ObjectId("5b08cefd0a8a7614c70a5713"),
"entity" : ObjectId("5b08ceb40a8a7614c70a5711"),
"name" : "Sub-B"
}
En een paar klassen om ze aan te binden, net als zeer eenvoudige voorbeelden:
public class Entity
{
public ObjectId id;
public string name { get; set; }
}
public class Other
{
public ObjectId id;
public ObjectId entity { get; set; }
public string name { get; set; }
}
public class EntityWithOthers
{
public ObjectId id;
public string name { get; set; }
public IEnumerable<Other> others;
}
public class EntityWithOther
{
public ObjectId id;
public string name { get; set; }
public Other others;
}
Vragen
Vloeiende interface
var listNames = new[] { "A", "B" };
var query = entities.Aggregate()
.Match(p => listNames.Contains(p.name))
.Lookup(
foreignCollection: others,
localField: e => e.id,
foreignField: f => f.entity,
@as: (EntityWithOthers eo) => eo.others
)
.Project(p => new { p.id, p.name, other = p.others.First() } )
.Sort(new BsonDocument("other.name",-1))
.ToList();
Verzoek verzonden naar server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"localField" : "_id",
"foreignField" : "entity",
"as" : "others"
} },
{ "$project" : {
"id" : "$_id",
"name" : "$name",
"other" : { "$arrayElemAt" : [ "$others", 0 ] },
"_id" : 0
} },
{ "$sort" : { "other.name" : -1 } }
]
Waarschijnlijk het gemakkelijkst te begrijpen, aangezien de vloeiende interface in principe hetzelfde is als de algemene BSON-structuur. De $lookup
stage heeft allemaal dezelfde argumenten en de $arrayElemAt
wordt weergegeven met First()
. Voor de $sort
je kunt gewoon een BSON-document of een andere geldige uitdrukking aanleveren.
Een alternatief is de nieuwere expressieve vorm van $lookup
met een subpijplijnverklaring voor MongoDB 3.6 en hoger.
BsonArray subpipeline = new BsonArray();
subpipeline.Add(
new BsonDocument("$match",new BsonDocument(
"$expr", new BsonDocument(
"$eq", new BsonArray { "$$entity", "$entity" }
)
))
);
var lookup = new BsonDocument("$lookup",
new BsonDocument("from", "others")
.Add("let", new BsonDocument("entity", "$_id"))
.Add("pipeline", subpipeline)
.Add("as","others")
);
var query = entities.Aggregate()
.Match(p => listNames.Contains(p.name))
.AppendStage<EntityWithOthers>(lookup)
.Unwind<EntityWithOthers, EntityWithOther>(p => p.others)
.SortByDescending(p => p.others.name)
.ToList();
Verzoek verzonden naar server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"let" : { "entity" : "$_id" },
"pipeline" : [
{ "$match" : { "$expr" : { "$eq" : [ "$$entity", "$entity" ] } } }
],
"as" : "others"
} },
{ "$unwind" : "$others" },
{ "$sort" : { "others.name" : -1 } }
]
De Fluent "Builder" ondersteunt de syntaxis nog niet rechtstreeks, en LINQ Expressions ondersteunt ook niet de $expr
operator, maar u kunt nog steeds construeren met behulp van BsonDocument
en BsonArray
of andere geldige uitdrukkingen. Hier "typen" we ook de $unwind
resultaat om een $sort
. toe te passen een uitdrukking gebruiken in plaats van een BsonDocument
zoals eerder getoond.
Afgezien van ander gebruik, is een primaire taak van een "subpijplijn" het verminderen van de documenten die worden geretourneerd in de doelarray van $lookup
. Ook de $unwind
hier dient om daadwerkelijk te worden "samengevoegd" in de $lookup
instructie over de uitvoering van de server, dus dit is doorgaans efficiënter dan alleen het eerste element van de resulterende array te pakken.
Opvraagbare groepslidmaatschap
var query = entities.AsQueryable()
.Where(p => listNames.Contains(p.name))
.GroupJoin(
others.AsQueryable(),
p => p.id,
o => o.entity,
(p, o) => new { p.id, p.name, other = o.First() }
)
.OrderByDescending(p => p.other.name);
Verzoek verzonden naar server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"localField" : "_id",
"foreignField" : "entity",
"as" : "o"
} },
{ "$project" : {
"id" : "$_id",
"name" : "$name",
"other" : { "$arrayElemAt" : [ "$o", 0 ] },
"_id" : 0
} },
{ "$sort" : { "other.name" : -1 } }
]
Dit is bijna identiek, maar gebruikt alleen de andere interface en produceert een iets andere BSON-verklaring, en eigenlijk alleen vanwege de vereenvoudigde naamgeving in de functionele instructies. Dit brengt de andere mogelijkheid naar voren om eenvoudig een $unwind
. te gebruiken zoals geproduceerd uit een SelectMany()
:
var query = entities.AsQueryable()
.Where(p => listNames.Contains(p.name))
.GroupJoin(
others.AsQueryable(),
p => p.id,
o => o.entity,
(p, o) => new { p.id, p.name, other = o }
)
.SelectMany(p => p.other, (p, other) => new { p.id, p.name, other })
.OrderByDescending(p => p.other.name);
Verzoek verzonden naar server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"localField" : "_id",
"foreignField" : "entity",
"as" : "o"
}},
{ "$project" : {
"id" : "$_id",
"name" : "$name",
"other" : "$o",
"_id" : 0
} },
{ "$unwind" : "$other" },
{ "$project" : {
"id" : "$id",
"name" : "$name",
"other" : "$other",
"_id" : 0
}},
{ "$sort" : { "other.name" : -1 } }
]
Normaal gesproken plaatst u een $unwind
direct volgend op $lookup
is eigenlijk een "geoptimaliseerd patroon" voor het aggregatieraamwerk. Het .NET-stuurprogramma verpest dit echter in deze combinatie door een $project
. te forceren ertussenin in plaats van de impliciete naamgeving op de "as"
. te gebruiken . Als dat niet zo is, is dit eigenlijk beter dan de $arrayElemAt
wanneer u weet dat u een "één" gerelateerd resultaat heeft. Als je de $unwind
. wilt "coalescentie", dan kun je beter de vloeiende interface gebruiken, of een andere vorm zoals later wordt aangetoond.
Querable Natuurlijk
var query = from p in entities.AsQueryable()
where listNames.Contains(p.name)
join o in others.AsQueryable() on p.id equals o.entity into joined
select new { p.id, p.name, other = joined.First() }
into p
orderby p.other.name descending
select p;
Verzoek verzonden naar server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"localField" : "_id",
"foreignField" : "entity",
"as" : "joined"
} },
{ "$project" : {
"id" : "$_id",
"name" : "$name",
"other" : { "$arrayElemAt" : [ "$joined", 0 ] },
"_id" : 0
} },
{ "$sort" : { "other.name" : -1 } }
]
Allemaal redelijk bekend en eigenlijk alleen maar functionele naamgeving. Net als bij het gebruik van de $unwind
optie:
var query = from p in entities.AsQueryable()
where listNames.Contains(p.name)
join o in others.AsQueryable() on p.id equals o.entity into joined
from sub_o in joined.DefaultIfEmpty()
select new { p.id, p.name, other = sub_o }
into p
orderby p.other.name descending
select p;
Verzoek verzonden naar server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"localField" : "_id",
"foreignField" : "entity",
"as" : "joined"
} },
{ "$unwind" : {
"path" : "$joined", "preserveNullAndEmptyArrays" : true
} },
{ "$project" : {
"id" : "$_id",
"name" : "$name",
"other" : "$joined",
"_id" : 0
} },
{ "$sort" : { "other.name" : -1 } }
]
Wat eigenlijk de "geoptimaliseerde coalescentie"-vorm gebruikt. De vertaler staat nog steeds op het toevoegen van een $project
aangezien we de tussenliggende select
. nodig hebben om de verklaring geldig te maken.
Samenvatting
Er zijn dus nogal wat manieren om in wezen te komen tot wat in wezen dezelfde query-instructie is met precies dezelfde resultaten. Terwijl u de JSON "kon" ontleden naar BsonDocument
vorm en voer dit naar de vloeiende Aggregate()
commando, is het over het algemeen beter om de natuurlijke builders of de LINQ-interfaces te gebruiken, omdat ze gemakkelijk op hetzelfde statement kunnen worden toegewezen.
De opties met $unwind
worden grotendeels getoond omdat zelfs met een "enkelvoud" overeenkomst die "coalescentie" vorm eigenlijk veel optimaler is dan het gebruik van $arrayElemAt
om het "eerste" array-element te nemen. Dit wordt zelfs nog belangrijker met overwegingen van zaken als de BSON-limiet waarbij de $lookup
doelarray kan ertoe leiden dat het bovenliggende document 16 MB overschrijdt zonder verdere filtering. Er is een ander bericht hier op Aggregate $lookup Totale grootte van documenten in overeenkomende pijplijn overschrijdt de maximale documentgrootte, waar ik feitelijk bespreek hoe te voorkomen dat die limiet wordt bereikt door dergelijke opties of andere Lookup()
te gebruiken syntaxis alleen beschikbaar voor de vloeiende interface op dit moment.