Jika Anda mencari "hal yang tepat" sebagai posting yang direferensikan untuk dilakukan dengan .NET, maka itu mungkin tidak akan benar-benar diimplementasikan seperti itu. Anda dapat melakukannya, tetapi Anda mungkin tidak akan repot-repot dan benar-benar memilih salah satu alternatif lain, kecuali jika Anda memerlukan "interval fleksibel" seperti yang saya lakukan..
Agregat Lancar
Jika Anda memiliki server MongoDB 3.6 atau lebih baru yang tersedia maka Anda dapat menggunakan $dateFromParts
untuk merekonstruksi tanggal dari bagian "bulat" yang diambil dari tanggal:
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();
Pernyataan dikirim ke server:
[
{ "$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 } }
]
Jika Anda tidak memiliki fitur itu, maka Anda dapat membiarkannya dan membiarkan tanggal "terbongkar", tetapi kemudian merakitnya lagi saat Anda memproses kursor. Hanya untuk mensimulasikan dengan daftar:
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 }
}
);
}
Pernyataan dikirim ke server:
[
{ "$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 } }
]
Ada sedikit perbedaan antara keduanya dalam hal kode. Hanya saja dalam satu kasus "casting back" ke DateTime
sebenarnya terjadi di server dengan $dateFromParts
dan di sisi lain kami hanya melakukan casting yang sama persis menggunakan DateTime
konstruktor dalam kode saat Anda mengulangi setiap hasil kursor.
Jadi mereka benar-benar hampir sama dengan satu-satunya perbedaan nyata adalah di mana "server" melakukan casting tanggal yang dikembalikan menggunakan byte jauh lebih sedikit per dokumen. Sebenarnya "5 kali" lebih sedikit karena semua format numerik di sini (termasuk Tanggal BSON) didasarkan pada bilangan bulat 64 bit. Meski begitu, semua angka itu sebenarnya masih "lebih ringan" daripada mengirim kembali representasi "string" dari sebuah tanggal.
LINQ Dapat Dikueri
Itu adalah bentuk dasar yang benar-benar tetap sama saat dipetakan ke bentuk yang berbeda ini:
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() };
Pernyataan dikirim ke server:
[
{ "$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" } }
]
Atau menggunakan 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);
Pernyataan dikirim ke server:
[
{ "$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 } }
]
Seperti yang Anda lihat, pada dasarnya bentuknya sama
Mengonversi yang Asli
Jika Anda ingin mereplikasi formulir "matematika tanggal" asli seperti yang diposting, maka formulir tersebut saat ini berada di luar cakupan yang sebenarnya dapat Anda lakukan dengan LINQ atau pembuat Lancar. Satu-satunya cara untuk mendapatkan urutan yang sama adalah dengan BsonDocument
konstruksi:
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();
Permintaan dikirim ke server:
[
{ "$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 } }
]
Alasan utama kami tidak dapat melakukan ini sekarang adalah karena serialisasi pernyataan saat ini pada dasarnya tidak setuju dengan poin yang dikatakan .NET Framework bahwa mengurangkan dua DateTime
nilai mengembalikan TimeSpan
, dan konstruksi MongoDB dari pengurangan dua Tanggal BSON mengembalikan "milidetik sejak zaman", yang pada dasarnya adalah cara kerja matematika.
Terjemahan "harfiah" dari ekspresi lamdba pada dasarnya adalah:
p => epoch.AddMilliseconds(
(p.Timestamp - epoch).TotalMilliseconds
- ((p.Timestamp - epoch).TotalMilliseconds % 1000 * 60 * 15))
Namun pemetaan masih membutuhkan beberapa pekerjaan untuk mengenali pernyataan atau memformalkan jenis pernyataan yang sebenarnya dimaksudkan untuk tujuan ini.
Khususnya MongoDB 4.0 memperkenalkan $convert
operator dan alias umum dari $toLong
dan $toDate
, yang semuanya dapat digunakan dalam pipa sebagai pengganti penanganan saat ini pada "penambahan" dan "pengurangan" dengan Tanggal BSON. Ini mulai membentuk spesifikasi yang lebih "formal" untuk konversi semacam itu daripada metode seperti yang ditunjukkan yang hanya mengandalkan "penambahan" dan "pengurangan", yang masih valid, tetapi operator bernama tersebut jauh lebih jelas maksudnya dalam kode:
{ "$group": {
"_id": {
"$toDate": {
"$subtract": [
{ "$toLong": "$Timestamp" },
{ "$mod": [{ "$toLong": "$Timestamp" }, 1000 * 60 * 15 ] }
]
}
},
"count": { "$sum": 1 }
}}
Cukup jelas untuk melihat bahwa dengan operator "formal" untuk konstruksi pernyataan dengan LINQ untuk fungsi "DateToLong" dan "LongToDate" seperti itu, maka pernyataan menjadi jauh lebih bersih tanpa jenis "pemaksaan" yang ditunjukkan dalam ekspresi lambda "tidak berfungsi" menjadi selesai.