Няма нужда да анализирате 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()
синтаксис, достъпен за плавния интерфейс само в момента.