Không cần phải phân tích cú pháp JSON. Mọi thứ ở đây thực sự có thể được thực hiện trực tiếp với LINQ hoặc giao diện Aggregate Fluent.
Chỉ sử dụng một số lớp trình diễn vì câu hỏi không thực sự mang lại nhiều điều để tiếp tục.
Thiết lập
Về cơ bản, chúng tôi có hai bộ sưu tập ở đây, là
thực thể
{ "_id" : ObjectId("5b08ceb40a8a7614c70a5710"), "name" : "A" }
{ "_id" : ObjectId("5b08ceb40a8a7614c70a5711"), "name" : "B" }
và những người khác
{
"_id" : ObjectId("5b08cef10a8a7614c70a5712"),
"entity" : ObjectId("5b08ceb40a8a7614c70a5710"),
"name" : "Sub-A"
}
{
"_id" : ObjectId("5b08cefd0a8a7614c70a5713"),
"entity" : ObjectId("5b08ceb40a8a7614c70a5711"),
"name" : "Sub-B"
}
Và một vài lớp để ràng buộc chúng, giống như những ví dụ rất cơ bản:
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;
}
Truy vấn
Giao diện thông thạo
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();
Yêu cầu được gửi đến máy chủ:
[
{ "$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 } }
]
Có lẽ là dễ hiểu nhất vì giao diện thông thạo về cơ bản giống với cấu trúc BSON chung. $lookup
giai đoạn có tất cả các đối số giống nhau và $arrayElemAt
được biểu diễn bằng First()
. Đối với $sort
bạn có thể chỉ cần cung cấp tài liệu BSON hoặc biểu thức hợp lệ khác.
Thay thế là hình thức biểu đạt mới hơn của $lookup
với một tuyên bố đường ống phụ cho MongoDB 3.6 trở lên.
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();
Yêu cầu được gửi đến máy chủ:
[
{ "$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" chưa hỗ trợ cú pháp trực tiếp, cũng như Biểu thức LINQ không hỗ trợ $expr
, tuy nhiên, bạn vẫn có thể xây dựng bằng cách sử dụng BsonDocument
và BsonArray
hoặc các biểu thức hợp lệ khác. Ở đây chúng tôi cũng "gõ" $unwind
dẫn đến việc áp dụng $sort
sử dụng một biểu thức thay vì một BsonDocument
như được hiển thị trước đó.
Ngoài các mục đích sử dụng khác, nhiệm vụ chính của "đường ống con" là giảm bớt các tài liệu được trả về trong mảng mục tiêu của $lookup
. Cũng là $unwind
ở đây phục vụ mục đích thực sự được "hợp nhất" vào $lookup
trên thực thi máy chủ, vì vậy điều này thường hiệu quả hơn là chỉ lấy phần tử đầu tiên của mảng kết quả.
Tham gia nhóm có thể truy vấn
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);
Yêu cầu được gửi đến máy chủ:
[
{ "$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 } }
]
Điều này gần như giống hệt nhau nhưng chỉ sử dụng giao diện khác và tạo ra một câu lệnh BSON hơi khác và thực sự chỉ vì cách đặt tên đơn giản trong các câu lệnh chức năng. Điều này mang lại một khả năng khác là chỉ cần sử dụng $unwind
như được tạo ra từ một 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);
Yêu cầu được gửi đến máy chủ:
[
{ "$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 } }
]
Thường đặt $unwind
trực tiếp theo dõi $lookup
thực sự là một "mẫu được tối ưu hóa" cho khung tổng hợp. Tuy nhiên, trình điều khiển .NET sẽ làm rối tung điều này trong sự kết hợp này bằng cách buộc một $project
ở giữa thay vì sử dụng tên ngụ ý trên "as"
. Nếu không phải vì điều đó, điều này thực sự tốt hơn $arrayElemAt
khi bạn biết bạn có "một" kết quả liên quan. Nếu bạn muốn $unwind
"kết hợp", thì tốt hơn hết bạn nên sử dụng giao diện thông thạo hoặc một hình thức khác như được trình bày ở phần sau.
Tự nhiên có thể xếp hạng
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;
Yêu cầu được gửi đến máy chủ:
[
{ "$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 } }
]
Tất cả đều khá quen thuộc và thực sự chỉ là đặt tên theo chức năng. Cũng giống như khi sử dụng $unwind
tùy chọn:
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;
Yêu cầu được gửi đến máy chủ:
[
{ "$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 } }
]
Mà thực sự đang sử dụng hình thức "liên kết tối ưu hóa". Người dịch vẫn khăng khăng thêm một $project
vì chúng ta cần select
trung gian để làm cho tuyên bố hợp lệ.
Tóm tắt
Vì vậy, có khá nhiều cách để đi đến cơ bản là cùng một câu lệnh truy vấn với kết quả chính xác như nhau. Trong khi bạn "có thể" phân tích cú pháp JSON thành BsonDocument
hình thành và cấp dữ liệu này vào Aggregate()
thông thạo , nói chung tốt hơn là sử dụng các trình xây dựng tự nhiên hoặc các giao diện LINQ vì chúng dễ dàng ánh xạ vào cùng một câu lệnh.
Các tùy chọn với $unwind
phần lớn được hiển thị bởi vì ngay cả với một đối sánh "số ít" thì dạng "kết hợp" thực sự tối ưu hơn nhiều khi sử dụng $arrayElemAt
để lấy phần tử mảng "đầu tiên". Điều này thậm chí còn trở nên quan trọng hơn khi xem xét những thứ như Giới hạn BSON nơi $lookup
mảng đích có thể khiến tài liệu mẹ vượt quá 16MB mà không cần lọc thêm. Có một bài đăng khác ở đây trên Aggregate $ lookup Tổng kích thước của tài liệu trong đường dẫn phù hợp vượt quá kích thước tài liệu tối đa, nơi tôi thực sự thảo luận về cách tránh giới hạn đó bị ảnh hưởng bằng cách sử dụng các tùy chọn như vậy hoặc Lookup()
khác cú pháp chỉ có sẵn cho giao diện thông thạo tại thời điểm này.