使用MongoDB C#驱动程序仅按日期进行过滤

20
我在我的项目中使用最新的MongoDB C#驱动程序,即3.+。我有不同的日期过滤条件,如今天、昨天、上个月等,通过使用daterangepicker
这是我的模型:
public class Student
{
 public Student()
 {
 }

 [BsonId]
 [BsonRepresentation(BsonType.ObjectId)]
 public string Id { get; set; }
 [BsonDateTimeOptions(Kind = DateTimeKind.Local)]
 public DateTime CreatedOn { get; set; }
 [BsonDateTimeOptions(Kind = DateTimeKind.Local)]
 public DateTime ModifiedOn { get; set; }
 public string Name { get; set; }
 public string Description { get; set; }
}

这是驱动程序代码:
var server = new MongoClient(_connectionString);
var db = server.GetDatabase("Students");
var collection = db.GetCollection<Student>("student");
var filterBuilder = Builders<Student>.Filter;
var start = new DateTime(2017, 03, 29);
var end = new DateTime(2017, 03, 31);
var filter = filterBuilder.Gte(x => x.CreatedOn, new BsonDateTime(start)) &
             filterBuilder.Lte(x => x.CreatedOn, new BsonDateTime(end));
List<Student> searchResult = collection.Find(filter).ToList();

这段代码运行良好,但当我选择今天的筛选器时,日期会变成
var start = new DateTime(2017, 03, 31);
var end = new DateTime(2017, 03, 31);

它没有返回当前日期的记录。它也在计算时间。

我将日期保存为DateTime.Now。我查询的示例ISO日期如下:

"CreatedOn": ISODate("2017-03-31T20:27:12.914+05:00"),
"ModifiedOn": ISODate("2017-03-31T20:27:12.914+05:00"),

这是我正在使用的日期过滤器。我是否需要从结束日期中减去1? 日期示例图片 需要帮助,我做错了什么。

你能否添加一个样本文档到你的集合中,以便于你期望查询当天返回结果? - s7vr
我正在将DateTime.Now保存在数据库中,请查看我的编辑后的问题。 - Ghazanfar Khan
感谢@Veeram今天的示例,我可以使用AddDays方法,但其他方法呢?我已经更新了日期过滤器。需要一种通用解决方案。 - Ghazanfar Khan
如果我必须在服务器端过滤日期,那daterangepicker有什么用呢?我能在客户端做吗? - Ghazanfar Khan
4个回答

31
我相信你对时区,特别是偏移量这个部分感到困惑。
MongoDb总是保存UTC时间的日期时间。
因此,当您查看MongoDB中的日期时间时,您必须始终考虑到与本地时区的偏移量。
您将始终以本地时区发送日期。在持久化之前,Mongo C#驱动程序会将时间从本地时间转换为UTC时间。
例如
当我保存文档并使用CreatedOn = 2017-04-05 15:21:23.234 (美国/芝加哥的本地时区)时, 但是当您查看数据库中的文档时,您将看到ISODate("2017-04-05T20:21:23.234Z"),即UTC偏移量为-5小时的本地时间。 [BsonDateTimeOptions(Kind = DateTimeKind.Local)]指示驱动程序在将BSON反序列化回POCO时将时间从UTC转换为本地时间。
以下是说明行为的测试用例。 代码:
class Program
{

    static void Main(string[] args)
    {
        var mongo = new MongoClient("mongodb://localhost:27017/test");
        var db = mongo.GetDatabase("test");

        db.DropCollection("students");
        db.CreateCollection("students");

        var collection = db.GetCollection<Student>("students");

        var today = DateTime.Now; //2017-04-05 15:21:23.234
        var yesterday = today.AddDays(-1);//2017-04-04 15:21:23.234

        // Create 2 documents (yesterday &  today)
        collection.InsertMany(new[]
            {
            new Student{Description = "today", CreatedOn = today},
            new Student{Description = "yesterday", CreatedOn = yesterday},
            }
         );

        var filterBuilder1 = Builders<Student>.Filter;
        var filter1 = filterBuilder1.Eq(x => x.CreatedOn, today);
        List<Student> searchResult1 = collection.Find(filter1).ToList();

        Console.Write(searchResult1.Count == 1);

        var filterBuilder2 = Builders<Student>.Filter;
        var filter2 = filterBuilder2.Eq(x => x.CreatedOn, yesterday);
        List<Student> searchResult2 = collection.Find(filter2).ToList();

        Console.Write(searchResult2.Count == 1);

    }
}

public class Student
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; set; }
    [BsonDateTimeOptions(Kind = DateTimeKind.Local)]
    public DateTime CreatedOn { get; set; }
    public string Description { get; set; }
}

集合:(通过 mongo shell 查看)

{
        "_id" : ObjectId("58e559c76d3a9d2cb0449d84"),
        "CreatedOn" : ISODate("2017-04-04T20:21:23.234Z"),
        "Description" : "yesterday"
}
{
        "_id" : ObjectId("58e559c76d3a9d2cb0449d85"),
        "CreatedOn" : ISODate("2017-04-05T20:21:23.234Z"),
        "Description" : "today"
}

更新:

"CreatedOn": ISODate("2017-03-31T20:27:12.914+05:00")

你的比较不起作用的原因是

 var start = new DateTime(2017, 03, 31);
 var end = new DateTime(2017, 03, 31);

这将作为$gteISODate("2017-03-31T00:00:00.000+05:00")更大,并且比ISODate("2017-03-31T00:00:00.000+05:00")更小的方式发送到服务器,但它没有找到上述条目。

查询今天日期的正确方法如下:

 var start = new DateTime(2017, 03, 31);
 var end = new DateTime(2017, 04, 01);

并更新您的过滤器以

var filter = filterBuilder.Gte(x => x.CreatedOn, start) &
         filterBuilder.Lt(x => x.CreatedOn, end);

现在您的范围查询已经作为$gteISODate("2017-03-31T00:00:00.000+05:00")之间的条件以及$ltISODate("2017-04-01T00:00:00.000+05:00")之间的条件发送到服务器,这样您应该能够找到今天的所有匹配项。

更新2

将您的数据库更改为将日期时间的时间部分设置为00:00:00。这将从数据库中删除时间部分,并且您的旧范围查询将在所有情况下正常工作。

更改您的保存方法以使用

var today = DateTime.Today; //2017-03-31 00:00:00.000

您可以返回到旧的过滤器定义。

类似以下操作

 var start = new DateTime(2017, 03, 31);
 var end = new DateTime(2017, 03, 31);

并更新您的筛选器为

var filter = filterBuilder.Gte(x => x.CreatedOn, start) &
         filterBuilder.Lte(x => x.CreatedOn, end);

现在你的范围查询已发送到服务器作为$gteISODate("2017-03-31T00:00:00.000+05:00")的比较,以及$lteISODate("2017-03-31T00:00:00.000+05:00")的比较,你应该能够找到所有今天的匹配项。

更新3 - 使用BsonDocument进行日期比较。

这里的想法是添加时区偏移量+5:00到服务器的UTC日期,并将计算出的日期时间转换为字符串yyyy-MM-dd格式,使用$dateToSting运算符后跟输入字符串日期的相同格式进行比较。

这将在你的时区中工作,但不适用于观察夏令时的时区。

Mongo版本3.4

您可以使用$addFields阶段添加新字段CreatedOnDate,同时保留所有现有属性和最后的$project,在比较后从最终响应中删除CreatedOnDate

Shell查询:

{
    "$addFields": {
        "CreatedOnDate": {
            "$dateToString": {
                "format": "%Y-%m-%d",
                "date": {
                    "$add": ["$CreatedOn", 18000000]
                }
            }
        }
    }
}, {
    "$match": {
        "CreatedOnDate": {
            "$gte": "2017-03-31",
            "$lte": "2017-03-31"
        }
    }
}, {
    "$project": {
        "CreatedOnDate": 0
    }
}

C#代码:

var start = new DateTime(2017, 03, 31);
var end = new DateTime(2017, 03, 31);

var addFields = BsonDocument.Parse("{$addFields: { CreatedOnDate: { $dateToString: { format: '%Y-%m-%d', date: {$add: ['$CreatedOn', 18000000] }} }} }");

var match = new BsonDocument("CreatedOnDate", new BsonDocument("$gte", start.ToString("yyyy-MM-dd")).Add("$lte", end.ToString("yyyy-MM-dd")));

var project = new BsonDocument
     {
       { "CreatedOnDate", 0 }
     };

var pipeline = collection.Aggregate().AppendStage<BsonDocument>(addFields)
    .Match(match)
    .Project(project);

var list = pipeline.ToList();

List<Student> searchResult = list.Select(doc => BsonSerializer.Deserialize<Student>(doc)).ToList();

Mongo版本 = 3.2

与上面相同,但此流水线使用$project,因此您必须添加要在最终响应中保留的所有字段。

Shell查询:

{
    "$project": {
        "CreatedOn": 1,
        "Description": 1,
        "CreatedOnDate": {
            "$dateToString": {
                "format": "%Y-%m-%d",
                "date": {
                    "$add": ["$CreatedOn", 18000000]
                }
            }
        }
    }
}, {
    "$match": {
        "CreatedOnDate": {
            "$gte": "2017-03-31",
            "$lte": "2017-03-31"
        }
    }
}, {
    "$project": {
        "CreatedOn": 1,
        "Description": 1
    }
}

C# 代码:

var start = new DateTime(2017, 03, 31);
var end = new DateTime(2017, 03, 31);

var project1 = new BsonDocument
    {
        { "CreatedOn", 1 },
        { "Description", 1 },
        { "CreatedOnDate", new BsonDocument("$dateToString", new BsonDocument("format", "%Y-%m-%d")
                            .Add("date", new BsonDocument("$add", new BsonArray(new object[] { "$CreatedOn", 5 * 60 * 60 * 1000 }))))
        }
    };

var match = new BsonDocument("CreatedOnDate", new BsonDocument("$gte", start.ToString("yyyy-MM-dd")).Add("$lte", end.ToString("yyyy-MM-dd")));

var project2 = new BsonDocument
    {
        { "CreatedOn", 1 },
        { "Description", 1 }
    };


var pipeline = collection.Aggregate()
.Project(project1)
.Match(match)
.Project(project2);

var list = pipeline.ToList();

List<Student> searchResult = list.Select(doc => BsonSerializer.Deserialize<Student>(doc)).ToList();

更新 4 - 只比较日期,并考虑夏令时的影响。

Mongo 版本 = 3.6

除了 $dateToString 将使用时区而不是固定偏移量之外,其他都保持不变,这应该考虑到夏令时的变化。

Shell 更新:

{
    "$addFields": {
        "CreatedOnDate": {
            "$dateToString": {
                "format": "%Y-%m-%d",
                "date": "$CreatedOn",
                "timezone": "America/New_York"
            }
        }
    }
}

C# 更新:

var addFields = BsonDocument.Parse("{$addFields: { CreatedOnDate: { $dateToString: { format: '%Y-%m-%d', date: "$CreatedOn", "timezone": "America/New_York"} }} }");

我想要存储时间部分,同时如何在你的答案中使用它?你没有使用DateTime.Now。 - Ghazanfar Khan
在示例中添加时间部分。对于比较查询,请使用答案中“更新”部分的filterDefinition - s7vr
如果我必须在服务器端过滤日期,那么daterangepicker有什么用处?我能在客户端做吗? - Ghazanfar Khan
更新了答案,将创建记录时的时间部分设置为00:00:00。这样做将会进行日期比较而非日期时间比较。请尝试一下并告诉我您的发现。 - s7vr
基本上我想存储日期和时间,但是只过滤日期记录。因为我的用户界面也显示时间。 - Ghazanfar Khan
抱歉,我找不到一种类型安全的方法来进行仅限日期的比较。一个选项是使用BsonDocument发送字符串日期值,并通过偏移时间差与服务器字符串日期进行比较,然后将响应映射回域对象,我已经更新了答案以采用这种方法。另一个选项是在将结束日期发送到服务器之前添加+1天,并使用第一个更新来过滤数据。让我知道你的想法。 - s7vr

1
使用带有过滤器的Collection.Find()。在您的存储库模式或任何用于数据库查询的代码中添加以下内容:
public async Task<List<T>> FindAll(Expression<Func<T, bool>> filter)
{
 try
 {
   // Fetch the filtered list from the Collection
   List<T> documents = await Collection.Find(filter).ToListAsync();
   // return the list
   return documents;
  }
  catch (Exception ex)
  {
   return await Task.FromResult(new List<T>() { });
  }
}

这样称呼它:
_someClass.FindAll(x => x.UpdatedAt > DateTime.UtcNow.AddDays(-5))

_someClass 应该被包含 FindAll 函数的类的实例所替代。


0

以下代码将显示您的 POCO 属性

    private DateTime _ShortDateOnly;
    [BsonElement("ShortDateOnly")]
    [BsonDateTimeOptions(DateOnly = true)]
    public DateTime ShortDateOnly {
        set { _ShortDateOnly = value; }
        get { return _ShortDateOnly.Date; }
    }

我用这个来创建以下 BSON 文档。
{{ 
    "_id" : CSUUID("12ce2538-2921-4da0-8211-9202da92d7f3"), 
    "first" : "Felipe", 
    "New" : "Ferreira", 
    "PublicPassword" : null, 
    "netWorth" : "20000.99", 
    "netWorth2" : 20000.990000000002, 
    "UserType" : "Admin", 
    "BirthDate" : ISODate("2019-06-22T18:59:01.861Z"), 
    "ShortDateOnly" : ISODate("2019-06-22T00:00:00Z") 
}}

请您将以下内容添加到您的POCO中,然后告诉我这是否足以让您的工作最小化?


-2

当你使用

new DateTime(2017, 03, 31);

你会得到一个DateTime对象,这意味着它也计算时间。 因此,通过使用相同的解析来处理开始和停止时间,实际上你会得到以下内容:

var start = var end = new DateTime("31/03/2017 00:00:00.00");

没错,你可能在这个特定的时间范围内没有记录。 如果你真的想获取今天的所有记录,你应该这样做:

var start = new DateTime("31/03/2017 00:00:00.00");
var end =   new DateTime("31/03/2017 23:59:59.99");

@GhazanfarKhan他在这里帮助你。如果他错了,请告诉他。没有必要使用这样的语言。希望你能理解。 - Sachin
明白了,但我没有预料到这个。 - Ghazanfar Khan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接