MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Обобщаване на $lookup с C#

Няма нужда да анализирате JSON. Всичко тук всъщност може да се направи директно с интерфейсите LINQ или Aggregate Fluent.

Просто използвам някои демонстрационни класове, защото въпросът всъщност не дава много за продължаване.

Настройка

По принцип имаме две колекции тук, като

субекти

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

идруги

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

И няколко класа, с които да ги обвържем, също като много основни примери:

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

Запитвания

Fluent интерфейс

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

Заявката е изпратена до сървъра:

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

Вероятно най-лесният за разбиране, тъй като плавният интерфейс е основно същият като общата структура на BSON. $lookup етапът има всички същите аргументи и $arrayElemAt се представя с First() . За $sort можете просто да предоставите BSON документ или друг валиден израз.

Алтернатива е по-новата изразителна форма на $lookup с изявление за под-тръбопровод за MongoDB 3.6 и по-нова версия.

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

Заявката е изпратена до сървъра:

[ 
  { "$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“ все още не поддържа директно синтаксиса, нито LINQ Expressions поддържа $expr оператор, но все пак можете да конструирате с помощта на BsonDocument и BsonArray или други валидни изрази. Тук ние също "набираме" $unwind резултат, за да приложите $sort използвайки израз, а не BsonDocument както е показано по-рано.

Освен други употреби, основна задача на "под-тръбопровода" е да намали документите, върнати в целевия масив на $lookup . Също така $unwind тук служи за цел действително да бъде "слято" в $lookup изявление за изпълнение на сървъра, така че това обикновено е по-ефективно от просто грабване на първия елемент от получения масив.

Присъединяване към група за запитване

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

Заявката е изпратена до сървъра:

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

Това е почти идентично, но просто използва различния интерфейс и произвежда малко по-различен израз на BSON и наистина само поради опростеното именуване във функционалните оператори. Това извежда другата възможност просто да използвате $unwind както е произведено от 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);

Заявката е изпратена до сървъра:

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

Обикновено се поставя $unwind директно след $lookup всъщност е "оптимизиран модел" за рамката за агрегиране. Въпреки това .NET драйверът обърка това в тази комбинация, като принуди $project между тях, вместо да използвате подразбиращото се именуване на "as" . Ако не за това, това всъщност е по-добро от $arrayElemAt когато знаете, че имате "един" свързан резултат. Ако искате $unwind „коалесценция“, тогава е по-добре да използвате плавния интерфейс или друга форма, както е показано по-късно.

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;

Заявката е изпратена до сървъра:

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

Всичко е доста познато и наистина се свежда само до функционалното именуване. Точно както при използването на $unwind опция:

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;

Заявката е изпратена до сървъра:

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

Което всъщност използва формата за "оптимизирана коалесценция". Преводачът все още настоява за добавяне на $project тъй като се нуждаем от междинния select за да направи изявлението валидно.

Резюме

Така че има доста начини по същество да стигнете до това, което по същество е същият израз на заявка с точно същите резултати. Докато „можете“ да анализирате JSON в BsonDocument оформете и задайте това на течния Aggregate() команда, като цяло е по-добре да използвате естествените конструктори или интерфейсите LINQ, тъй като те лесно се преобразуват в едно и също изявление.

Опциите с $unwind са до голяма степен показани, защото дори и при „единствено“ съвпадение тази форма на „сливане“ всъщност е много по-оптимална от използването на $arrayElemAt за да вземете "първия" елемент от масива. Това дори става по-важно с оглед на неща като BSON Limit, където $lookup целевият масив може да причини родителския документ да надвиши 16MB без допълнително филтриране. Тук има още една публикация в Aggregate $lookup Общият размер на документите в съответстващия конвейер надвишава максималния размер на документа, където всъщност обсъждам как да избегнем достигането на това ограничение чрез използване на такива опции или друг Lookup() синтаксис, достъпен за плавния интерфейс само в момента.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Как да създадете DB за MongoDB контейнер при стартиране?

  2. (MongoDB Java) $push в масив

  3. Как да възстановите конкретна колекция в MongoDB с помощта на логическо архивиране

  4. Добавете нов валидатор към съществуващата колекция

  5. Кой SchemaType в Mongoose е най-добър за времеви печат?