sql >> Databáze >  >> NoSQL >> MongoDB

Agregovat $lookup s C#

Není potřeba analyzovat JSON. Vše zde lze ve skutečnosti provést přímo pomocí rozhraní LINQ nebo Aggregate Fluent.

Stačí použít některé demonstrační třídy, protože otázka ve skutečnosti nedává moc k pokračování.

Nastavení

V podstatě zde máme dvě sbírky, jsou to

subjekty

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

a další

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

A pár tříd, se kterými je lze svázat, stejně jako velmi základní příklady:

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

Dotazy

Plynulé rozhraní

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

Požadavek odeslán na 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 } }
]

Pravděpodobně nejsnáze pochopitelné, protože plynulé rozhraní je v podstatě stejné jako obecná struktura BSON. $lookup stage má všechny stejné argumenty a $arrayElemAt je reprezentován pomocí First() . Pro $sort můžete jednoduše zadat dokument BSON nebo jiný platný výraz.

Alternativou je novější výrazová forma $lookup s příkazem sub-pipeline pro MongoDB 3.6 a vyšší.

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

Požadavek odeslán na 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 } }
]

Fluent "Builder" zatím nepodporuje syntaxi přímo, ani výrazy LINQ nepodporují $expr operátor, ale stále můžete konstruovat pomocí BsonDocument a BsonArray nebo jiné platné výrazy. Zde také "napíšeme" $unwind výsledek, aby bylo možné použít $sort pomocí výrazu namísto BsonDocument jak bylo uvedeno dříve.

Kromě jiných použití je primárním úkolem "sub-pipeline" snížit množství dokumentů vrácených v cílovém poli $lookup . Také $unwind zde slouží účelu skutečného „sloučení“ do $lookup při spuštění serveru, takže je to obvykle efektivnější než pouhé zachycení prvního prvku výsledného pole.

Dotazovatelné GroupJoin

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

Požadavek odeslán na 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 } }
]

To je téměř identické, ale pouze s použitím jiného rozhraní a vytváří mírně odlišný příkaz BSON, a to opravdu jen kvůli zjednodušenému pojmenování ve funkčních příkazech. To přináší další možnost jednoduše použít $unwind vytvořené z 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);

Požadavek odeslán na 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 } }
]

Normálně umístění $unwind přímo po $lookup je ve skutečnosti "optimalizovaný vzor" pro agregační rámec. Ovladač .NET to však v této kombinaci zkazí tím, že vynutí $project spíše než použití implikovaného pojmenování v "as" . Pokud ne, je to ve skutečnosti lepší než $arrayElemAt když víte, že máte „jeden“ související výsledek. Pokud chcete $unwind „slučování“, pak je lepší použít plynulé rozhraní nebo jinou formu, jak bude ukázáno později.

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;

Požadavek odeslán na 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 } }
]

Všechno docela známé a opravdu jen funkční pojmenování. Stejně jako při použití $unwind možnost:

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;

Požadavek odeslán na 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 } }
]

Což ve skutečnosti používá formu „optimalizované koalescence“. Překladatel stále trvá na přidání $project protože potřebujeme prostřední select aby bylo prohlášení platné.

Shrnutí

Existuje tedy poměrně málo způsobů, jak v podstatě dospět k tomu, co je v podstatě stejný příkaz dotazu s přesně stejnými výsledky. Zatímco jste „mohli“ analyzovat JSON na BsonDocument formulář a vložte jej do plynulého Aggregate() příkazu, je obecně lepší použít přirozené stavitele nebo rozhraní LINQ, protože se snadno mapují na stejný příkaz.

Možnosti pomocí $unwind jsou z velké části zobrazeny, protože i při „singulární“ shodě je tato „slučovací“ forma ve skutečnosti mnohem optimálnější než použití $arrayElemAt vzít "první" prvek pole. To se stává ještě důležitějším s ohledem na věci, jako je BSON Limit, kde $lookup cílové pole by mohlo způsobit, že nadřazený dokument překročí 16 MB bez dalšího filtrování. Je zde další příspěvek na Aggregate $lookup Celková velikost dokumentů v odpovídajícím kanálu překračuje maximální velikost dokumentu, kde ve skutečnosti diskutuji o tom, jak se vyhnout dosažení tohoto limitu použitím takových možností nebo jiných Lookup() syntaxe je v současné době k dispozici pouze pro plynulé rozhraní.




  1. redis cluster průběžně tiskne protokol WSA_IO_PENDING

  2. Zkombinujte dvě instance Redis do jedné instance se dvěma databázemi

  3. Jak nainstalovat a nakonfigurovat MongoDB na Ubuntu

  4. Odstranění reference one-one a one-many - Mongoose