Hvis du ser på den "præcise ting" som det refererede indlæg, der har at gøre med .NET, så vil det sandsynligvis ikke blive implementeret sådan. Det kan du godt, men du kommer nok ikke til at gå til besværet og faktisk gå efter et af de andre alternativer, medmindre du har behov for "fleksible intervaller" i det omfang, jeg gør.
Flydende aggregat
Hvis du har en moderne MongoDB 3.6 eller nyere server tilgængelig, kan du bruge $dateFromParts
for at rekonstruere datoen fra de "afrundede" dele udtrukket fra datoen:
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();
Udtalelse sendt til 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 } }
]
Hvis du ikke har den funktion tilgængelig, så kan du simpelthen lade den være fra og lade datoen være "skilt ad", men derefter samle den igen, mens du behandler markøren. Bare for at simulere med en liste:
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 }
}
);
}
Erklæring sendt til serveren:
[
{ "$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 } }
]
Der er meget lidt forskel mellem de to med hensyn til koden. Det er bare, at i et tilfælde "casting back" til DateTime
sker faktisk på serveren med $dateFromParts
og i den anden laver vi netop den samme casting ved hjælp af DateTime
konstruktør i kode, mens du gentager hvert markørresultat.
Så de er næsten de samme, med den eneste reelle forskel er, hvor "serveren" caster, og den returnerede dato bruger meget færre bytes pr. dokument. Faktisk "5 gange" mindre, da alle numeriske formater her (inklusive BSON-datoen) er baseret på 64 bit heltal. Alligevel er alle disse tal faktisk stadig "lettere" end at sende en "streng"-repræsentation af en dato tilbage.
LINQ kan forespørges
Det er de grundlæggende former, som virkelig forbliver de samme, når de kortlægges på disse forskellige former:
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() };
Erklæring sendt til serveren:
[
{ "$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" } }
]
Eller ved at bruge 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);
Erklæring sendt til serveren:
[
{ "$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 } }
]
Som du kan se, er det hele grundlæggende den samme form
Konvertering af originalen
Hvis du ønsker at replikere den originale "date math"-formular som opslået, falder den i øjeblikket uden for rækkevidden af, hvad du faktisk kan gøre med enten LINQ eller Fluent Builders. Den eneste måde at få den samme sekvens på er med BsonDocument
konstruktion:
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();
Anmodning sendt til 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 } }
]
Den store grund til, at vi ikke kan gøre dette lige nu, er, at den aktuelle serialisering af udsagn dybest set er uenig med det punkt, at .NET Framework siger, at man trækker to DateTime
fra. værdier returnerer en TimeSpan
, og MongoDB-konstruktionen med at subtrahere to BSON-datoer returnerer "millisekunder siden epoke", hvilket i bund og grund er, hvordan matematikken fungerer.
Den "bogstavelige" oversættelse af lamdba-udtrykket er i det væsentlige:
p => epoch.AddMilliseconds(
(p.Timestamp - epoch).TotalMilliseconds
- ((p.Timestamp - epoch).TotalMilliseconds % 1000 * 60 * 15))
Men kortlægningen kræver stadig noget arbejde for enten at genkende udsagn eller formalisere, hvilken slags udsagn der faktisk er beregnet til dette formål.
Især MongoDB 4.0 introducerer $convert
operator og de almindelige aliasser for $toLong
og $toDate
, som alle kan bruges i pipelinen i stedet for den nuværende håndtering af "addition" og "subtraktion" med BSON Dates. Disse begynder at danne en mere "formel" specifikation for sådanne konverteringer snarere end metoden som vist, som udelukkende var afhængig af den "addition" og "subtraktion", som stadig er gyldige, men sådanne navngivne operatorer er meget tydeligere i hensigten i koden:
{ "$group": {
"_id": {
"$toDate": {
"$subtract": [
{ "$toLong": "$Timestamp" },
{ "$mod": [{ "$toLong": "$Timestamp" }, 1000 * 60 * 15 ] }
]
}
},
"count": { "$sum": 1 }
}}
Det er ret tydeligt at se, at med "formaliserede" operatorer til sætningskonstruktion med LINQ for sådanne "DateToLong"- og "LongToDate"-funktioner, så bliver sætningen meget renere, uden at de typer af "tvang", der vises i lambdaudtrykket "ikke-fungerende" er færdig.