JSONを解析する必要はありません。ここでのすべては、実際にはLINQまたはAggregateFluentインターフェースのいずれかを使用して直接実行できます。
質問はあまり進んでいないので、いくつかのデモンストレーションクラスを使用するだけです。
セットアップ
基本的に、ここには2つのコレクションがあります。
エンティティ
{ "_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;
}
クエリ
流暢なインターフェース
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
の新しい表現形式です。 MongoDB3.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式は$expr
をサポートしていません。 演算子。ただし、BsonDocument
を使用して作成することもできます。 およびBsonArray
または他の有効な式。ここでは、$unwind
も「入力」します $sort
を適用するための結果 BsonDocument
ではなく式を使用する 前に示したように。
他の用途とは別に、「サブパイプライン」の主なタスクは、$lookup
のターゲット配列で返されるドキュメントを減らすことです。 。また、$unwind
ここでは、実際に$lookup
に「マージ」されるという目的を果たします。 サーバー実行に関するステートメント。したがって、これは通常、結果の配列の最初の要素を取得するよりも効率的です。
クエリ可能な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);
サーバーに送信されたリクエスト:
[
{ "$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
よりも優れています 「1つの」関連する結果があることがわかっている場合。 $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
を使用するよりも、「合体」形式の方が実際にははるかに最適であるという「特異な」一致があっても、主に示されています。 「最初の」配列要素を取得します。これは、$lookup
であるBSON制限などを考慮するとさらに重要になります。 ターゲット配列により、さらにフィルタリングせずに親ドキュメントが16MBを超える可能性があります。 Aggregate $ lookupに関する別の投稿があります。一致するパイプライン内のドキュメントの合計サイズが最大ドキュメントサイズを超えています。このようなオプションやその他のLookup()
を使用して、制限に達するのを回避する方法について実際に説明しています。 現時点でのみ流暢なインターフェースで使用できる構文。