无法使用 C# 驱动程序筛选 MongoDB 中的日期字段

Unable to filter on Date fields in MongoDB using C# Driver

我正在使用 Newtonsoft JsonTextWriter 手动序列化多个 POCO,并将结果保存为 MongoDB BsonDocument。

//
// POCO to store in MongoDB
public class Session
{

    public DateTime? StartUTCTimestamp { get; set; }

    public DateTime? StartTimestamp { get; set; }

    public DateTime? EndTimestamp { get; set; }

    public void ToJSON(ref JsonTextWriter writer)
    {
        Session session = this;            

        writer.WriteStartObject(); // {

        writer.WritePropertyName("StartUTCTimestamp");
        writer.WriteValue(session.StartUTCTimestamp);

        writer.WritePropertyName("StartTimestamp");
        writer.WriteValue(session.StartTimestamp);

        writer.WritePropertyName("EndTimestamp");
        writer.WriteValue(session.EndTimestamp);


        writer.WriteEndObject(); // }
    }
}

测试应用程序中用于导入数据的方法,从 SQL 服务器数据库检索所有 Session 对象(使用 Telerik 的 Open Access ORM)将结果存储在列表中。我通过调用在 POCO 上定义的 ToJSON() 方法(见上文)序列化每个会话,传递对 JsonTextWriter 的引用。然后使用 C# MongoDB 驱动程序将生成的 JSON 字符串反序列化为 BsonDocument,然后将其保存到 Mongo。 (下面的示例来自 ASP.NET Web 表单页面,因此是警告框用户控件)。

private void LoadData()
{
    DateTime startDate = new DateTime(2015,12,31,23,59,59);
    var collection = _database.GetCollection<BsonDocument>("sessions");

    using (DbContext ctx = new DbContext())
    {
        List<Session> sessions = ctx.Sessions.Where().ToList();

        foreach (Session item in sessions)
        {
            JsonTextWriter writer = null;
            try
            {
                StringWriter sw = new StringWriter();
                writer = new JsonTextWriter(sw);
                writer.CloseOutput = true;
                item.ToJSON(ref writer);
                String json = sw.ToString();

                BsonDocument doc = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>(json);

                collection.InsertOne(doc);
                this.ucAlertMsg.Show("bg-info", "Completed without exception");
            }
            catch (Exception ex)
            {
                while (ex.InnerException != null) { ex = ex.InnerException; }
                this.ucAlertMsg.Show("bg-danger", ex.Message);
            }
            finally
            {
                writer.Close();
            }
        }
    }
}

这样可以很好地保存文档,但是,我无法有效地查询文档,在日期范围内进行过滤。我相信,根据我读过的其他几篇文章和文章,这可能是因为该值存储为字符串而不是 "ISODate()".

// 
// This is what IS saved
{
    "_id" : ObjectId("5729128cd9017a248cbe6284"),
    "StartUTCTimestamp" : "2015-12-15T23:24:06",
    "StartTimestamp" : "2015-12-15T18:24:06",
    "EndTimestamp" : "2015-12-15T18:26:59",
}

//
// Is this what I need? 
{
    "_id" : ObjectId("5729128cd9017a248cbe6284"),
    "StartUTCTimestamp" : ISODate("2015-12-15T23:24:06"),
    "StartTimestamp" :  ISODate("2015-12-15T18:24:06"),
    "EndTimestamp" :  ISODate("2015-12-15T18:26:59"),
}

在我的 LoadData() 方法中,我在 TextWriter 上尝试了一些配置,根据我读过的一些文章,这些配置似乎应该有所帮助......

StringWriter sw             = new StringWriter();
writer                      = new JsonTextWriter(sw);
writer.DateFormatHandling   = DateFormatHandling.IsoDateFormat;
writer.CloseOutput          = true;

将 "IsoDateFormat" 分配给写入器上的 "DateFormatHanding" 设置没有产生任何差异。我也试过 "MicrosoftDateFormat",数据仍然存储为字符串,但格式不同。

真题

这就是所有设置...问题是 "how do I search MongoDB documents based on date"?

使用 MongoDB 的 C# 驱动程序允许我使用 Linq 进行搜索。这是我正在使用的 linq 查询。

IMongoCollection<Session> collection = database.GetCollection<Session>("sessions");
DateTime startDate  = (this.StartDate.HasValue) ? this.StartDate.Value : DateTime.Now.AddDays(-7);
DateTime endDate    = (this.EndDate.HasValue) ? this.EndDate.Value : DateTime.Now;
var data            = collection.Find<Session>(e => e.StartTimestamp.Value >= startDate && e.StartTimestamp.Value <= endDate).ToList();

由于 JSON 直接映射回 Session POCO,我应该可以使用该类型 (?)。我可以成功过滤 Session POCO 中的其他字段。这只是让我适合的日期。

我猜测我的实现中可能存在问题或疏忽,或者由于数据是以字符串形式存储的,因此无法与 Date(?) 进行比较。

如有任何见解,我们将不胜感激。

谢谢, -G

没有多少人看过这个 post 所以也许我没有给它贴上标签,但是对于一个投票的人来说,我确实遇到了我认为 "work around" 而不是实际的东西解决方案,但也许它也会对您有所帮助。

除了以典型的字符串格式 ("yyy/MM/dd HH:mm:ss") 保存 dates 之外,我还把日期保存为 Int64 ticks。 parser/writer 会将这些识别为数字并存储它们。当然,数字很容易分类和排序。

所以原来 post 的 Session 模型现在看起来像这样......

//
// POCO to store in MongoDB
public class Session
{

    public DateTime? StartUTCTimestamp { get; set; }

    public DateTime? StartTimestamp { get; set; }

    public DateTime? EndTimestamp { get; set; }


    //
    // If the StartUTCDate is defined then return the number of "ticks" in the date
    [BsonElement("StartUTCTimestampTicks")]
    public Int64? StartUTCTimestampTicks 
    { 
        get 
        { 
            return (this.StartUTCTimestamp.HasValue) ?
                    (Int64?)this.StartUTCTimestamp.Value.Ticks : 
                    null; 
        } 
    }

    //
    // If the StartDate is defined then return the number of "ticks" in the date
    [BsonElement("StartTimestampTicks")]
    public Int64? StartTimestampTicks
    { 
        get 
        { 
            return (this.StartTimestamp.HasValue) ?
                    (Int64?)this.StartTimestamp.Value.Ticks : 
                    null; 
        } 
    }

    //
    // If the EndDate is defined then return the number of "ticks" in the date
    [BsonElement("EndTimestampTicks")]
    public Int64? EndTimestampTicks
    { 
        get 
        { 
            return (this.EndTimestamp.HasValue) ?
                    (Int64?)this.EndTimestamp.Value.Ticks : 
                    null; 
        } 
    }

    public void ToJSON(ref JsonTextWriter writer)
    {
        Session session = this;            

        writer.WriteStartObject(); // {


        if (session.StartUTCTimestamp.HasValue)
        {
            writer.WritePropertyName("StartUTCTimestamp");
            writer.WriteValue(session.StartUTCTimestamp);

            writer.WritePropertyName("StartUTCTimestampTicks");
            writer.WriteValue(session.StartUTCTimestampTicks);
        }

        if (session.StartTimestamp.HasValue)
        {
            writer.WritePropertyName("StartTimestamp");
            writer.WriteValue(session.StartTimestamp);

            writer.WritePropertyName("StartTimestampTicks");
            writer.WriteValue(session.StartTimestampTicks);
        }

        if (session.EndTimestamp.HasValue)
        {
            writer.WritePropertyName("EndTimestamp");
            writer.WriteValue(session.EndTimestamp);

            writer.WritePropertyName("EndTimestampTicks");
            writer.WriteValue(session.EndTimestampTicks);
        }


        writer.WriteEndObject(); // }
    }
}

我添加了显示每个日期中刻度数的属性(如果已定义)。如果不是,他们只是 return null。我确实必须用 BsonElement 属性来装饰它们,使它们可以被 JsonTextWriter 识别(不知道为什么这些需要它而其他属性不需要),否则我会收到一个错误。

我还修改了模型上的 ToJSON() 方法,以检查在序列化值之前是否提供了日期。如果未定义日期,那么我们就不会将 json 元素添加到文档中。

存储在 MongoDB 中的结果文档现在看起来像...

{
    "_id" : ObjectId("572b4486d9016b151846b8ed"),
    "StartUTCTimestamp" : "2016-04-24T17:02:12",
    "StartUTCTimestampTicks" : NumberLong(635971141320000000),
    "StartTimestamp" : "2016-04-24T13:02:12",
    "StartTimestampTicks" : NumberLong(635970997320000000),
    "EndTimestamp" : "2016-04-24T13:05:16",
    "EndTimestampTicks" : NumberLong(635970999160000000)
}

为什么这些值是用 "NumberLong()" 数据类型函数存储的,而日期不是用 "ISODate()" 函数存储的,这对我来说是个谜,但它现在允许我查询日期和日期基于刻度而不是日期字符串的范围。

IMongoCollection<Session> collection = database.GetCollection<Session>("sessions");
DateTime startDate  = (this.StartDate.HasValue) ? this.StartDate.Value : DateTime.Now.AddDays(-7);
DateTime endDate    = (this.EndDate.HasValue) ? this.EndDate.Value : DateTime.Now;
var data            = collection.Find<Session>(e => e.StartTimestampTicks.Value >= startDate.Ticks && e.StartTimestampTicks.Value <= endDate.Ticks).ToList();

这实际上让我考虑根本不保存日期字符串。我将它们更多地保留为标签,因为在查看实际的 JsonDocument 时它们更具可读性。但是,只要文档的主要用途是通过用户界面查看,将 Ticks 转换为更易读的日期字符串表示形式以供查看是一件微不足道的事情。

无论如何...我希望这能帮助...某人...:)

过滤表达式中的日期必须使用 BsonValue.Create(yourDate) 创建;

示例:

   IMongoDatabase db = GetMongoDbConnection();
   IMongoCollection<BsonDocument> collection = db.GetCollection<BsonDocument> ("yourCollectionName");
   DateTime date = BsonValue.Create(DateTime.Now.Date);
   var filter = Builders<BsonDocument>.Filter.Gt("dateFieldToFilterOn", date);
   List<BsonDocument> bsonDocuments = await collection.Find(filter).ToListAsync();