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

Samlet $lookup med C#

Der er ingen grund til at parse JSON. Alt her kan faktisk gøres direkte med enten LINQ eller Aggregate Fluent interfaces.

Bare ved at bruge nogle demonstrationsklasser, fordi spørgsmålet ikke rigtig giver meget at gå på.

Opsætning

Grundlæggende har vi to samlinger her, nemlig

enheder

{ "_id" :ObjectId("5b08ceb40a8a7614c70a5710"), "name" :"A" }{ "_id" :ObjectId("5b08ceb40a8a7614c70a5711"), "name" :"B" . 

og andre

{ "_id" :ObjectId("5b08cef10a8a7614c70a5712"), "entity" :ObjectId("5b08ceb40a8a7614c70a5710"), "name" :"Sub-A"}{ "Id(61c40a57a"), "Sub-A"}{ "8ceb40a8a7614c70a5710"}{ "81d" :0c3d7a5a") , "entity" :ObjectId("5b08ceb40a8a7614c70a5711"), "name" :"Sub-B"}

Og et par klasser at binde dem til, lige som meget grundlæggende eksempler:

public class Entity{ public ObjectId id; public string name { get; sæt; }}public class Andet{ public ObjectId id; public ObjectId entity { get; sæt; } offentlig strengnavn { get; sæt; }}public class EntityWithOthers{ public ObjectId id; public string name { get; sæt; } public IEnumerable others;} public class EntityWithOther{ public ObjectId id; public string name { get; sæt; } offentlig Andre andre;} 

Forespørgsler

Flydende grænseflade

var listNames =new[] { "A", "B" };var query =entities.Aggregate() .Match(p => listNames.Contains(p.name)) .Lookup( foreignCollection:andre, localField:e => e.id, fremmedField: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(); 

Anmodning sendt til 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 } }]

Sandsynligvis den nemmeste at forstå, da den flydende grænseflade stort set er den samme som den generelle BSON-struktur. $lookup stage har alle de samme argumenter og $arrayElemAt er repræsenteret med First() . For $sort du kan blot levere et BSON-dokument eller et andet gyldigt udtryk.

En alternativ er den nyere udtryksform af $lookup med en sub-pipeline-erklæring for MongoDB 3.6 og nyere.

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("fra", "andre") .Add("let", new BsonDocument("entity", "$_id ")) .Add("pipeline", subpipeline) .Add("as","others"));var query =entities.Aggregate() .Match(p => listNames.Contains(p.name)) .AppendStage (opslag) .Slap af(p => p.others) .SortByDescending(p => p.others.name) .ToList(); 

Anmodning sendt til 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 } }]

Den flydende "Builder" understøtter ikke syntaksen direkte endnu, og LINQ Expressions understøtter heller ikke $expr operator, men du kan stadig konstruere ved hjælp af BsonDocument og BsonArray eller andre gyldige udtryk. Her "skriver" vi også $unwind resultat for at anvende en $sort ved at bruge et udtryk i stedet for et BsonDocument som vist tidligere.

Bortset fra andre anvendelser er en primær opgave for en "sub-pipeline" at reducere de dokumenter, der returneres i målarrayet $lookup . Også $unwind her tjener det formål faktisk at blive "fusioneret" i $lookup sætning om serverudførelse, så dette er typisk mere effektivt end blot at få fat i det første element i det resulterende array.

Forespørgselsvenlig gruppetilmelding

var query =entities.AsQueryable() .Where(p => listNames.Contains(p.name)) .GroupJoin( others.AsQueryable(), p => p.id, o => o. enhed, (p, o) => ny { p.id, p.name, other =o.First() } ) .OrderByDescending(p => p.other.name); 

Anmodning sendt til 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 } }]

Dette er næsten identisk, men bruger bare den anderledes grænseflade og producerer en lidt anderledes BSON-sætning, og egentlig kun på grund af den forenklede navngivning i de funktionelle sætninger. Dette viser den anden mulighed ved blot at bruge en $unwind som produceret fra en SelectMany() :

var query =entities.AsQueryable() .Where(p => listNames.Contains(p.name)) .GroupJoin( others.AsQueryable(), p => p.id, o => o. enhed, (p, o) => new { p.id, p.name, other =o } ) .SelectMany(p => p.other, (p, other) => new { p.id, p.name , andet }) .OrderByDescending(p => p.andet.navn); 

Anmodning sendt til 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 " :"$navn", "other" :"$other", "_id" :0 }}, { "$sort" :{ "other.name" :-1 } }]

Normalt placerer en $unwind direkte efter $lookup er faktisk et "optimeret mønster" for aggregeringsrammen. Men .NET-driveren ødelægger dette i denne kombination ved at tvinge et $projekt ind imellem i stedet for at bruge den underforståede navngivning på "as" . Hvis ikke for det, er dette faktisk bedre end $arrayElemAt når du ved, at du har "et" relateret resultat. Hvis du vil have $unwind "sammensmeltning", så er du bedre stillet ved at bruge den flydende grænseflade eller en anden form som vist senere.

Querable Natural

var query =fra p i entities.AsQueryable() hvor listNames.Contains(p.name) join o i others.AsQueryable() på p.id er lig med o.entity into joined vælg ny { p.id , p.navn, andet =joined.First() } i p ordreved p.other.name faldende vælg p; 

Anmodning sendt til 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 } }]

Alt sammen ret velkendt og egentlig bare ned til funktionel navngivning. Ligesom med at bruge $unwind mulighed:

var query =fra p i entities.AsQueryable() hvor listNames.Contains(p.name) join o i others.AsQueryable() på p.id er lig med o.entity into joined fra sub_o i joined.DefaultIfEmpty () vælg ny { p.id, p.name, other =sub_o } i p orderby p.other.name faldende vælg p; 

Anmodning sendt til 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" :{ "andet.navn" :-1 } }]

Hvilket faktisk bruger den "optimerede sammensmeltning"-form. Oversætteren insisterer stadig på at tilføje et $projekt da vi har brug for den mellemliggende select for at gøre erklæringen gyldig.

Oversigt

Så der er en hel del måder at komme frem til, hvad der grundlæggende er den samme forespørgselserklæring med nøjagtig de samme resultater. Mens du "kunne" parse JSON til BsonDocument form og feed dette til den flydende Aggregate() kommando, er det generelt bedre at bruge de naturlige bygherrer eller LINQ-grænseflader, da de nemt kan overføres til den samme sætning.

Indstillingerne med $unwind er stort set vist, fordi selv med et "singular" match er denne "sammensmeltnings"-form faktisk langt mere optimal end at bruge $arrayElemAt for at tage det "første" array-element. Dette bliver endda vigtigere med overvejelser om ting som BSON-grænsen, hvor $lookup target array kan få det overordnede dokument til at overstige 16 MB uden yderligere filtrering. Der er et andet indlæg her om Aggregate $lookup Den samlede størrelse af dokumenter i matchende pipeline overstiger den maksimale dokumentstørrelse, hvor jeg faktisk diskuterer, hvordan man undgår, at grænsen bliver ramt ved at bruge sådanne muligheder eller andre Lookup() syntaks er kun tilgængelig for den flydende grænseflade på nuværende tidspunkt.




  1. Mongoose sorterer det aggregerede resultat

  2. Hvordan deaktiverer man mongoDB java-driverlogning?

  3. Hvorfor bruger MongoDB ikke indekskryds?

  4. Gendan en MongoDB-database ved hjælp af mongorestore