MongoDB .NET驱动程序按时间范围分组

Pra*_*n93 1 c# linq mongodb aggregation-framework mongodb-.net-driver

我是MongoDB的新手,想知道如何计算从UTC时间12 AM到当前UTC时间每15分钟间隔插入集合中的文档总数。

以下是样本文件

{
    "_id" : ObjectId("5ade8bfc6b941c7726a54f01"),
    "Country" : "US"
    "Timestamp" : ISODate("2018-04-24T01:44:28.040Z"),
}
Run Code Online (Sandbox Code Playgroud)

这是预期的输出:

{
    "Count": 245,
    "ReceiveDateString": "5/2/2018 12:00:00 AM"
},
{
    "Count": 239,
    "ReceiveDateString": "5/2/2018 12:15:00 AM"
},
{
    "Count": 252,
    "ReceiveDateString": "5/2/2018 12:30:00 AM"
},
{
    "Count": 255,
    "ReceiveDateString": "5/2/2018 12:45:00 AM"
},
{
    "Count": 242,
    "ReceiveDateString": "5/2/2018 1:00:00 AM"
}
.
.
.

and so on until current UTC time.
Run Code Online (Sandbox Code Playgroud)

我可以按分钟分组,如下所示:

var filter = Builders<Model>.Filter.Where(r => r.Timestamp > startDate && r.Timestamp < endDate);
var result = Collection.Aggregate()
           .Match(filter)
           .Group(
               r => r.Timestamp.Minute,
               g => new
               {
                   ReceiveDate = g.Select(x => x.Timestamp).First(),
                   Count = g.Count(),
               }
           ).ToEnumerable();
Run Code Online (Sandbox Code Playgroud)

但是,我无法弄清楚如何将MongoDb中15分钟为间隔的Group result中提供的解决方案转换为MongoDB C#驱动程序查询。

谢谢。

Nei*_*unn 7

如果您将“确切的东西”视为与.NET相关的参考文章,则可能实际上并不会像这样实现。您可以这样做,但是除非您在我需要的范围内需要“灵活的间隔”,否则您可能不会费劲地去寻找其他选择之一。

流利的骨料

如果您拥有现代化的MongoDB 3.6或更高版本的服务器,则可以使用$dateFromParts以便从从日期中提取的“四舍五入”部分中重建日期:

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();
Run Code Online (Sandbox Code Playgroud)

发送到服务器的语句:

[
  { "$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 } }
]
Run Code Online (Sandbox Code Playgroud)

如果没有该功能,则可以简单地将其保留下来,并保留“反汇编”日期,然后在处理游标时再次将其汇编。只是为了模拟一个列表:

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 }
    }
  );
}
Run Code Online (Sandbox Code Playgroud)

发送到服务器的语句:

[
  { "$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 } }
]
Run Code Online (Sandbox Code Playgroud)

两者在代码方面几乎没有区别。只是在一种情况下,“投射回” DateTime实际上发生在服务器上,$dateFromParts而在另一种情况下,DateTime当您迭代每个游标结果时,我们使用代码中的构造函数进行了完全相同的转换。

因此,它们实际上几乎是相同的,唯一的区别是“服务器”强制转换返回的日期,每个文档使用的字节数要少得多。实际上,因为这里的所有数字格式(包括BSON Date)都是基于64位整数,所以减少了“ 5倍”。即便如此,所有这些数字实际上仍然比发送回日期的任何“字符串”表示“轻巧”。

LINQ可查询

这些是基本形式,当映射到这些不同形式时,它们实际上保持不变:

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() };
Run Code Online (Sandbox Code Playgroud)

发送到服务器的语句:

[
  { "$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" } }
]
Run Code Online (Sandbox Code Playgroud)

或使用 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);
Run Code Online (Sandbox Code Playgroud)

发送到服务器的语句:

[
  { "$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 } }
]
Run Code Online (Sandbox Code Playgroud)

如您所见,它们基本上都是相同的形式


转换原稿

如果您希望复制发布的原始“日期数学”表格,那么当前该表格超出了您可以使用LINQ或Fluent构建器实际执行的范围。获得相同序列的唯一方法是BsonDocument构造:

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();
Run Code Online (Sandbox Code Playgroud)

请求已发送到服务器:

[
  { "$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 } }
]
Run Code Online (Sandbox Code Playgroud)

我们现在无法执行此操作的主要原因是,当前的语句序列化基本上不同意.NET Framework的说法,即减去两个DateTime值将返回a TimeSpan,而减去两个BSON日期的MongoDB构造将返回“自纪元以来的毫秒数” ”,这实际上就是数学的工作原理。

lamdba表达式的“文字”翻译实质上是:

p =>  epoch.AddMilliseconds(
       (p.Timestamp - epoch).TotalMilliseconds
       - ((p.Timestamp - epoch).TotalMilliseconds % 1000 * 60 * 15))
Run Code Online (Sandbox Code Playgroud)

但是,映射仍然需要做一些工作,以便识别语句或正式确定针对此目的实际上打算使用哪种语句。

值得注意的是MongoDB的4.0引入了$convert运营商和共同的别名$toLong$toDate,这都可以在管道中使用以代替目前的处理上“加法”和“减法”与BSON日期。这些开始为这种转换形成了一个更为“正式”的规范,而不是所示的仅依赖于“加法”和“减法”的方法,该方法仍然有效,但是这样的命名运算符在代码中的意图更加清晰:

{ "$group": {
  "_id": {
    "$toDate": {
      "$subtract": [
        { "$toLong": "$Timestamp" },
        { "$mod": [{ "$toLong": "$Timestamp" }, 1000 * 60 * 15 ] }
      ]
    }
  },
  "count": { "$sum": 1 }
}}
Run Code Online (Sandbox Code Playgroud)

显而易见,使用LINQ的“形式化”运算符对此类“ DateToLong”和“ LongToDate”函数使用LINQ进行构造,则该语句变得更加整洁,而无需使用“非工作” lambda表达式中所示的“强制”类型。完成。