Nếu bạn đang tìm kiếm "điều chính xác" như bài đăng được tham chiếu phải làm với .NET, thì có lẽ nó sẽ không thực sự được triển khai như vậy. Bạn có thể làm điều đó, nhưng có lẽ bạn sẽ không bận tâm và thực sự đi tìm một trong những lựa chọn thay thế khác, trừ khi bạn có nhu cầu về "khoảng thời gian linh hoạt" ở mức độ mà tôi làm ..
Tổng hợp Thông thạo
Nếu bạn có sẵn máy chủ MongoDB 3.6 hiện đại trở lên thì bạn có thể sử dụng $dateFromParts
để tạo lại ngày từ các phần "làm tròn" được trích từ ngày:
DateTime startDate = new DateTime(2018, 5, 1, 0, 0, 0, DateTimeKind.Utc);
DateTime endDate = new DateTime(2018, 6, 1, 0, 0, 0, DateTimeKind.Utc);
var result = Collection.Aggregate()
.Match(k => k.Timestamp >= startDate && k.Timestamp < endDate)
.Group(k =>
new DateTime(k.Timestamp.Year, k.Timestamp.Month, k.Timestamp.Day,
k.Timestamp.Hour, k.Timestamp.Minute - (k.Timestamp.Minute % 15), 0),
g => new { _id = g.Key, count = g.Count() }
)
.SortBy(d => d._id)
.ToList();
Báo cáo được gửi đến máy chủ:
[
{ "$match" : {
"Timestamp" : {
"$gte" : ISODate("2018-05-01T00:00:00Z"),
"$lt" : ISODate("2018-06-01T00:00:00Z")
}
} },
{ "$group" : {
"_id" : {
"$dateFromParts" : {
"year" : { "$year" : "$Timestamp" },
"month" : { "$month" : "$Timestamp" },
"day" : { "$dayOfMonth" : "$Timestamp" },
"hour" : { "$hour" : "$Timestamp" },
"minute" : { "$subtract" : [
{ "$minute" : "$Timestamp" },
{ "$mod" : [ { "$minute" : "$Timestamp" }, 15 ] }
] },
"second" : 0
}
},
"count" : { "$sum" : 1 }
} },
{ "$sort" : { "_id" : 1 } }
]
Nếu bạn không có sẵn tính năng đó, thì bạn có thể chỉ cần tắt tính năng đó và để ngày "tháo rời", nhưng sau đó lắp ráp lại khi bạn xử lý con trỏ. Chỉ để mô phỏng với một danh sách:
var result = Collection.Aggregate()
.Match(k => k.Timestamp >= startDate && k.Timestamp < endDate)
.Group(k => new
{
year = k.Timestamp.Year,
month = k.Timestamp.Month,
day = k.Timestamp.Day,
hour = k.Timestamp.Hour,
minute = k.Timestamp.Minute - (k.Timestamp.Minute % 15)
},
g => new { _id = g.Key, count = g.Count() }
)
.SortBy(d => d._id)
.ToList();
foreach (var doc in result)
{
//System.Console.WriteLine(doc.ToBsonDocument());
System.Console.WriteLine(
new BsonDocument {
{ "_id", new DateTime(doc._id.year, doc._id.month, doc._id.day,
doc._id.hour, doc._id.minute, 0) },
{ "count", doc.count }
}
);
}
Báo cáo được gửi đến máy chủ:
[
{ "$match" : {
"Timestamp" : {
"$gte" : ISODate("2018-05-01T00:00:00Z"),
"$lt" : ISODate("2018-06-01T00:00:00Z")
}
} },
{ "$group" : {
"_id" : {
"year" : { "$year" : "$Timestamp" },
"month" : { "$month" : "$Timestamp" },
"day" : { "$dayOfMonth" : "$Timestamp" },
"hour" : { "$hour" : "$Timestamp" },
"minute" : { "$subtract" : [
{ "$minute" : "$Timestamp" },
{ "$mod" : [ { "$minute" : "$Timestamp" }, 15 ] }
] }
},
"count" : { "$sum" : 1 }
} },
{ "$sort" : { "_id" : 1 } }
]
Có rất ít sự khác biệt giữa cả hai về mã. Chỉ là trong một trường hợp, "truyền lại" thành DateTime
thực sự xảy ra trên máy chủ với $dateFromParts
và trong cách khác, chúng tôi chỉ thực hiện truyền chính xác đó bằng cách sử dụng DateTime
hàm tạo trong mã khi bạn lặp lại từng kết quả con trỏ.
Vì vậy, chúng thực sự gần như giống nhau với sự khác biệt thực sự duy nhất là nơi "máy chủ" thực hiện truyền ngày trả về sử dụng ít byte hơn rất nhiều trên mỗi tài liệu. Trên thực tế, ít hơn "5 lần" vì tất cả các định dạng số ở đây (bao gồm cả Ngày BSON) đều dựa trên số nguyên 64 bit. Mặc dù vậy, tất cả những con số đó vẫn thực sự "nhẹ" hơn so với việc gửi lại bất kỳ biểu diễn "chuỗi" nào của ngày.
LINQ Có thể truy vấn
Đó là những biểu mẫu cơ bản thực sự giữ nguyên khi ánh xạ vào các biểu mẫu khác nhau này:
var query = from p in Collection.AsQueryable()
where p.Timestamp >= startDate && p.Timestamp < endDate
group p by new DateTime(p.Timestamp.Year, p.Timestamp.Month, p.Timestamp.Day,
p.Timestamp.Hour, p.Timestamp.Minute - (p.Timestamp.Minute % 15), 0) into g
orderby g.Key
select new { _id = g.Key, count = g.Count() };
Báo cáo được gửi đến máy chủ:
[
{ "$match" : {
"Timestamp" : {
"$gte" : ISODate("2018-05-01T00:00:00Z"),
"$lt" : ISODate("2018-06-01T00:00:00Z")
}
} },
{ "$group" : {
"_id" : {
"$dateFromParts" : {
"year" : { "$year" : "$Timestamp" },
"month" : { "$month" : "$Timestamp" },
"day" : { "$dayOfMonth" : "$Timestamp" },
"hour" : { "$hour" : "$Timestamp" },
"minute" : { "$subtract" : [
{ "$minute" : "$Timestamp" },
{ "$mod" : [ { "$minute" : "$Timestamp" }, 15 ] }
] },
"second" : 0
}
},
"__agg0" : { "$sum" : 1 }
} },
{ "$sort" : { "_id" : 1 } },
{ "$project" : { "_id" : "$_id", "count" : "$__agg0" } }
]
Hoặc sử dụng GroupBy()
var query = Collection.AsQueryable()
.Where(k => k.Timestamp >= startDate && k.Timestamp < endDate)
.GroupBy(k =>
new DateTime(k.Timestamp.Year, k.Timestamp.Month, k.Timestamp.Day,
k.Timestamp.Hour, k.Timestamp.Minute - (k.Timestamp.Minute % 15), 0),
(k, s) => new { _id = k, count = s.Count() }
)
.OrderBy(k => k._id);
Báo cáo được gửi đến máy chủ:
[
{ "$match" : {
"Timestamp" : {
"$gte" : ISODate("2018-05-01T00:00:00Z"),
"$lt" : ISODate("2018-06-01T00:00:00Z")
}
} },
{ "$group" : {
"_id" : {
"$dateFromParts" : {
"year" : { "$year" : "$Timestamp" },
"month" : { "$month" : "$Timestamp" },
"day" : { "$dayOfMonth" : "$Timestamp" },
"hour" : { "$hour" : "$Timestamp" },
"minute" : { "$subtract" : [
{ "$minute" : "$Timestamp" },
{ "$mod" : [ { "$minute" : "$Timestamp" }, 15 ] }
] },
"second" : 0
}
},
"count" : { "$sum" : 1 }
} },
{ "$sort" : { "_id" : 1 } }
]
Như bạn có thể thấy, về cơ bản tất cả đều có cùng một dạng
Chuyển đổi bản gốc
Nếu bạn đang tìm cách sao chép biểu mẫu "toán ngày tháng" ban đầu như đã đăng, thì biểu mẫu này hiện nằm ngoài phạm vi những gì bạn thực sự có thể làm với LINQ hoặc các trình tạo Fluent. Cách duy nhất để có được cùng một chuỗi đó là sử dụng BsonDocument
xây dựng:
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var group = new BsonDocument { {
"$group",
new BsonDocument {
{ "_id",
new BsonDocument { {
"$add", new BsonArray
{
new BsonDocument { {
"$subtract",
new BsonArray {
new BsonDocument { { "$subtract", new BsonArray { "$Timestamp", epoch } } },
new BsonDocument { {
"$mod", new BsonArray
{
new BsonDocument { { "$subtract", new BsonArray { "$Timestamp", epoch } } },
1000 * 60 * 15
}
} }
}
} },
epoch
}
} }
},
{
"count", new BsonDocument("$sum", 1)
}
}
} };
var query = sales.Aggregate()
.Match(k => k.Timestamp >= startDate && k.Timestamp < endDate)
.AppendStage<BsonDocument>(group)
.Sort(new BsonDocument("_id", 1))
.ToList();
Yêu cầu được gửi đến máy chủ:
[
{ "$match" : {
"Timestamp" : {
"$gte" : ISODate("2018-05-01T00:00:00Z"),
"$lt" : ISODate("2018-06-01T00:00:00Z")
}
} },
{ "$group" : {
"_id" : {
"$add" : [
{ "$subtract" : [
{ "$subtract" : [ "$Timestamp", ISODate("1970-01-01T00:00:00Z") ] },
{ "$mod" : [
{ "$subtract" : [ "$Timestamp", ISODate("1970-01-01T00:00:00Z") ] },
900000
] }
] },
ISODate("1970-01-01T00:00:00Z")
]
},
"count" : { "$sum" : 1 }
} },
{ "$sort" : { "_id" : 1 } }
]
Lý do lớn nhất mà chúng tôi không thể thực hiện việc này ngay bây giờ là vì việc tuần tự hóa các câu lệnh hiện tại về cơ bản không đồng ý với điểm .NET Framework nói rằng trừ đi hai DateTime
các giá trị trả về một TimeSpan
và cấu trúc MongoDB trừ hai Ngày BSON trả về "mili giây kể từ kỷ nguyên", về cơ bản là cách toán học hoạt động.
Bản dịch "theo nghĩa đen" của biểu thức lamdba về cơ bản là:
p => epoch.AddMilliseconds(
(p.Timestamp - epoch).TotalMilliseconds
- ((p.Timestamp - epoch).TotalMilliseconds % 1000 * 60 * 15))
Nhưng việc ánh xạ vẫn cần một số công việc để có thể nhận ra các câu lệnh hoặc chính thức hóa loại câu lệnh nào thực sự dành cho mục đích này.
Đáng chú ý là MongoDB 4.0 giới thiệu $convert
toán tử và các bí danh chung của $toLong
và $toDate
, tất cả đều có thể được sử dụng trong quy trình thay cho xử lý hiện tại về "phép cộng" và "phép trừ" với BSON Dates. Chúng bắt đầu tạo thành một đặc tả "chính thức" hơn cho các chuyển đổi như vậy thay vì phương pháp như được hiển thị chỉ dựa vào "phép cộng" và "phép trừ" vẫn hợp lệ, nhưng các toán tử được đặt tên như vậy rõ ràng hơn nhiều về ý định trong mã:
{ "$group": {
"_id": {
"$toDate": {
"$subtract": [
{ "$toLong": "$Timestamp" },
{ "$mod": [{ "$toLong": "$Timestamp" }, 1000 * 60 * 15 ] }
]
}
},
"count": { "$sum": 1 }
}}
Khá đơn giản để thấy rằng với các toán tử được "chính thức hóa" để xây dựng câu lệnh với LINQ cho các hàm "DateToLong" và "LongToDate", thì câu lệnh trở nên gọn gàng hơn nhiều mà không có các loại "coercions" được hiển thị trong biểu thức lambda "không hoạt động". xong.