私は次のMongoDbクエリが機能しています:
db.Entity.aggregate(
[
{
"$match":{"Id": "12345"}
},
{
"$lookup": {
"from": "OtherCollection",
"localField": "otherCollectionId",
"foreignField": "Id",
"as": "ent"
}
},
{
"$project": {
"Name": 1,
"Date": 1,
"OtherObject": { "$arrayElemAt": [ "$ent", 0 ] }
}
},
{
"$sort": {
"OtherObject.Profile.Name": 1
}
}
]
)
これにより、別のコレクションから一致するオブジェクトと結合されたオブジェクトのリストが取得されます。
LINQまたはこの正確な文字列を使用してC#でこれを使用する方法を知っている人はいますか?
次のコードを使用してみましたが、QueryDocument
とMongoCursor
の型が見つからないようです-廃止されたと思いますか?
BsonDocument document = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>("{ name : value }");
QueryDocument queryDoc = new QueryDocument(document);
MongoCursor toReturn = _connectionCollection.Find(queryDoc);
JSONを解析する必要はありません。ここでのすべては、LINQまたはAggregate Fluentインターフェイスのいずれかで実際に直接実行できます。
質問は実際にはあまり多くのことを与えないため、いくつかのデモクラスを使用するだけです。
基本的に、ここには2つのコレクションがあります。
entities
{ "_id" : ObjectId("5b08ceb40a8a7614c70a5710"), "name" : "A" }
{ "_id" : ObjectId("5b08ceb40a8a7614c70a5711"), "name" : "B" }
およびothers
{
"_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 } }
]
流fluentなインターフェイスは基本的なBSON構造と基本的に同じなので、おそらく最も理解しやすいでしょう。 $lookup
ステージにはすべて同じ引数があり、 $arrayElemAt
はFirst()
で表されます。 $sort
の場合、BSONドキュメントまたは他の有効な式を指定するだけです。
別の方法は、MongoDB 3.6以降のサブパイプラインステートメントを使用した新しい表現形式の $lookup
です。
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
を使用して構築できますまたは他の有効な式。ここでは、示されているようにBsonDocument
ではなく式を使用して $unwind
を適用するために $sort
結果を「入力」しますついさっき。
他の用途とは別に、「サブパイプライン」の主なタスクは、ターゲット配列 $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ステートメントを生成します。これは、機能ステートメントの命名が単純化されているためです。これは、単にSelectMany()
から生成される $unwind
を使用する他の可能性をもたらします。
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
の直後に配置することは、実際には集約フレームワークの "optimized pattern" です。ただし、.NETドライバーは、$project
で暗黙の命名を使用するのではなく、その間に"as"
を強制することにより、この組み合わせでこれを台無しにします。そうでない場合、これは実際には $arrayElemAt
よりも優れています。これは、「1」関連の結果があることがわかっている場合です。 $unwind
"coalescence"が必要な場合は、Fluentインターフェイスを使用するか、後で説明する別のフォームを使用することをお勧めします。
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 } }
]
実際には "最適化された合体" 形式を使用しています。ステートメントを有効にするには中間のselect
が必要なので、翻訳者は $project
を追加することを引き続き主張します。
そのため、基本的に同じクエリステートメントで、まったく同じ結果が得られるものに到達するには、いくつかの方法があります。 JSONをBsonDocument
形式に「解析」し、これを流__なAggregate()
コマンドにフィードすることはできますが、通常は同じステートメントに簡単にマッピングされるため、ナチュラルビルダーまたはLINQインターフェイスを使用することをお勧めします。
$unwind
のオプションは、 "特異"マッチであっても $arrayElemAt
を使用するよりも実際に「合体」フォームがはるかに最適であるため、主に表示されます。 「最初の」配列要素を取得します。これは、 $lookup
ターゲット配列が親ドキュメントをさらにフィルタリングせずに16MBを超える可能性があるBSON Limitなどのことを考慮するとさらに重要になります。ここに別の投稿があります Aggregate $ lookup一致するパイプラインのドキュメントの合計サイズが最大ドキュメントサイズを超えています ここで、流suchに利用可能なそのようなオプションまたは他のLookup()
構文を使用して、その制限に達するのを回避する方法を実際に議論します現時点ではインターフェースのみ。
MongoDB.Entities でこれを行う方法を次に示します。 2つのエンティティが1対多または多対多の関係にある場合、以下に示すように、手動で結合する必要なく、逆の関係にアクセスできます。 [免責事項:私は図書館の著者です]
using System;
using System.Linq;
using MongoDB.Entities;
using MongoDB.Driver.Linq;
namespace StackOverflow
{
public class Program
{
public class Author : Entity
{
public string Name { get; set; }
public Many<Book> Books { get; set; }
public Author() => this.InitOneToMany(() => Books);
}
public class Book : Entity
{
public string Title { get; set; }
}
static void Main(string[] args)
{
new DB("test");
var book = new Book { Title = "The Power Of Now" };
book.Save();
var author = new Author { Name = "Eckhart Tolle" };
author.Save();
author.Books.Add(book);
//build a query for finding all books that has Power in the title.
var bookQuery = DB.Queryable<Book>()
.Where(b => b.Title.Contains("Power"));
//find all the authors of books that has a title with Power in them
var authors = author.Books
.ParentsQueryable<Author>(bookQuery); //also can pass in an ID or array of IDs
//get the result
var result = authors.ToArray();
//output the aggregation pipeline
Console.WriteLine(authors.ToString());
Console.ReadKey();
}
}
}