sql >> Database >  >> NoSQL >> MongoDB

Voeg $lookup samen met C#

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.




  1. Hoe maak ik een MongoDB-dump van mijn database?

  2. ObjectId matchen met string voor $graphLookup

  3. MongoDB repliceren in een hybride cloudomgeving

  4. StackExchange TimeoutException bij het invoegen van 750 items in 2 sets in redis