sql >> Databasteknik >  >> NoSQL >> MongoDB

Aggregera $lookup med C#

Det finns inget behov av att analysera JSON. Allt här kan faktiskt göras direkt med antingen LINQ eller Aggregate Fluent-gränssnittet.

Använder bara några demonstrationsklasser eftersom frågan egentligen inte ger så mycket att gå på.

Inställningar

I grund och botten har vi två samlingar här, nämligen

enheter

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

och andra

{
        "_id" : ObjectId("5b08cef10a8a7614c70a5712"),
        "entity" : ObjectId("5b08ceb40a8a7614c70a5710"),
        "name" : "Sub-A"
}
{
        "_id" : ObjectId("5b08cefd0a8a7614c70a5713"),
        "entity" : ObjectId("5b08ceb40a8a7614c70a5711"),
        "name" : "Sub-B"
}

Och ett par klasser att binda dem till, precis som väldigt grundläggande exempel:

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;
}

Frågor

Flytande gränssnitt

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();

Begäran skickad till servern:

[
  { "$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 } }
]

Förmodligen det enklaste att förstå eftersom det flytande gränssnittet i princip är detsamma som den allmänna BSON-strukturen. $lookup scenen har alla samma argument och $arrayElemAt representeras med First() . För $sort du kan helt enkelt tillhandahålla ett BSON-dokument eller annat giltigt uttryck.

Ett alternativ är den nyare uttrycksformen av $lookup med en sub-pipeline-förklaring för MongoDB 3.6 och senare.

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();

Begäran skickad till servern:

[ 
  { "$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 flytande "Builder" stöder inte syntaxen direkt ännu, och LINQ Expressions stöder inte heller $expr operator, men du kan fortfarande konstruera med BsonDocument och BsonArray eller andra giltiga uttryck. Här "skriver" vi också $unwind resultat för att tillämpa en $sort använder ett uttryck snarare än ett BsonDocument som visats tidigare.

Bortsett från andra användningsområden är en primär uppgift för en "sub-pipeline" att minska de dokument som returneras i målarrayen $lookup . Även $unwind här tjänar ett syfte att faktiskt "sammanfogas" i $lookup uttalande om serverexekvering, så detta är vanligtvis mer effektivt än att bara ta tag i det första elementet i den resulterande arrayen.

Frågbar gruppanslutning

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);

Begäran skickad till servern:

[ 
  { "$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 } }
]

Detta är nästan identiskt men använder bara det olika gränssnittet och producerar en något annorlunda BSON-sats, och egentligen bara på grund av den förenklade namngivningen i de funktionella satserna. Detta tar upp den andra möjligheten att helt enkelt använda en $unwind som produceras från en 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);

Begäran skickad till servern:

[
  { "$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 } }
]

Normalt placerar du en $unwind direkt efter $lookup är faktiskt ett "optimerat mönster" för aggregeringsramverket. Men .NET-drivrutinen förstör detta i denna kombination genom att tvinga fram ett $project däremellan istället för att använda det underförstådda namnet på "as" . Om inte för det är detta faktiskt bättre än $arrayElemAt när du vet att du har "ett" relaterat resultat. Om du vill ha $unwind "koalescens", då är det bättre att använda det flytande gränssnittet, eller en annan form som visas senare.

Querable Natural

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;

Begäran skickad till servern:

[
  { "$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 } }
]

Alla ganska bekanta och egentligen bara ner till funktionell namngivning. Precis som med att använda $unwind alternativ:

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;

Begäran skickad till servern:

[ 
  { "$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 } }
]

Vilket faktiskt använder formen "optimerad sammansmältning". Översättaren insisterar fortfarande på att lägga till ett $project eftersom vi behöver den mellanliggande select för att göra påståendet giltigt.

Sammanfattning

Så det finns en hel del sätt att i huvudsak komma fram till vad som i princip är samma frågesats med exakt samma resultat. Medan du "kunde" tolka JSON till BsonDocument formulär och mata detta till den flytande Aggregate() kommandot är det generellt sett bättre att använda de naturliga byggare eller LINQ-gränssnitten eftersom de enkelt kan mappas till samma uttalande.

Alternativen med $unwind visas till stor del för att även med en "singular" matchning är denna "sammansmältnings"-form faktiskt mycket mer optimal än att använda $arrayElemAt för att ta det "första" matriselementet. Detta blir till och med viktigare med hänsyn till saker som BSON-gränsen där $lookup målarray kan göra att det överordnade dokumentet överstiger 16 MB utan ytterligare filtrering. Det finns ett annat inlägg här om Aggregate $lookup Den totala storleken på dokument i matchande pipeline överstiger den maximala dokumentstorleken där jag faktiskt diskuterar hur man undviker att den gränsen träffas genom att använda sådana alternativ eller andra Lookup() syntax endast tillgänglig för det flytande gränssnittet för närvarande.




  1. Mongoose.js instance.save() callback aktiveras inte

  2. Resulterar varje anrop inom ett multi()-anrop i phpredis i en ny nätverksresa till redis?

  3. Länka och skapa MongoDB-anslutningar med SQL:Del 1

  4. Hur sparar jag och avslutar redis.conf?