【问题标题】:MongoDB Array QueryMongoDB 数组查询
【发布时间】:2014-05-24 00:50:18
【问题描述】:

我是 Mongo DB 的新手,我正在尝试弄清楚如何进行一些更复杂的查询。我有一个包含DateTime 嵌套数组的文档。

这是我的数据:

{ "_id" : ObjectId("537d0b8c2c6b912520798b76"), "FirstName" : "Mary", "LastName" : "Johnson", "Age" : 27, "Phone" : "555 555-1212", "Dates" : [ISODate("2014-05-21T21:34:16.378Z"), ISODate("1987-01-05T08:00:00Z")] }

{ "_id" : ObjectId("537e4a7e2c6b91371c684a34"), "FirstName" : "Walter", "LastName" : "White", "Age" : 52, "Phone" : "800 123-4567", "Dates" : [ISODate("1967-12-25T08:00:00Z"), ISODate("2014-12-25T08:00:00Z")] }

我想要做的是返回 Dates 数组包含一个范围之间的值的文档。在我的测试用例中,范围是 1/1/1987 和 1/10/1987,所以我希望取回上面列出的第一个文档(Mary Johnson),因为 1/5/1987 在该 Dates 数组中并且介于 1/1 之间/1987 年和 1987 年 1 月 10 日。

我希望能够同时使用 MongoDB 命令行实用程序和 C# 驱动程序来执行此操作。

使用 C# 驱动程序,我尝试了以下 LINQ 查询(基于 MongoDB 文档中的示例):

DateTime beginRange = new DateTime(1987, 1, 1);
DateTime endRange = new DateTime(1987, 1, 10);

var result = from p in people.AsQueryable<Person>()
    where p.Dates.Any(date => date > beginRange && date < endRange)
    select p;

以上代码从 C# 驱动程序代码中引发异常:

Any 仅支持序列化为文档的项目。当前的序列化程序是DateTimeSerializer,必须实现IBsonDocumentSerializer 才能参与Any 查询。

当我尝试直接查询 MongoDB 数据库时,我尝试了以下操作:

db.People.find( {Dates: { $gt: ISODate("1987-01-01"), $lt: ISODate("1987-01-10") } } )

此查询会返回两个文档,而不仅仅是在其Dates 数组中包含1/5/1987 的文档。

编辑:

我从 C# 驱动程序中找到了一种方法。它不像我想要的那样干净,但它是可行的。

我认为,既然有一种方法可以直接从命令实用程序中获取我想要的东西,那么如果从 C# 驱动程序中也必须有一种方法,只需从 C# 驱动程序执行相同的查询即可。

string command = "{Dates : { $elemMatch : { $gt: ISODate(\"" + beginRange.ToString("yyyy-MM-dd") + "\"), $lt: ISODate(\"" + endRange.ToString("yyyy-MM-dd") + "\") } } } ";
var bsonDoc = BsonSerializer.Deserialize<BsonDocument>(command);
var queryDoc = new QueryDocument(bsonDoc);
MongoCursor<Person> p = people.Find(queryDoc);

【问题讨论】:

    标签: c# .net linq mongodb mongodb-.net-driver


    【解决方案1】:

    C# 驱动程序

    正如例外所暗示的,只要您的数组是原始类型 (DateTime) 而不是真正的文档,您就无法使用 C# 驱动程序执行您想做的事情。

    来自MongoDB LinqAny的描述:

    这仅在可枚举的元素被序列化为文档时才起作用。有一个服务器错误阻止了它对原语起作用。

    我猜你可以围绕 DateTime 值创建一个文档包装器,这样你仍然可以这样做:

    var result = people.AsQueryable<Person>().Where(
        person => person.Dates.Any(date => 
            date.Value > beginRange && date.Value < endRange));
    

    .

    public class DocumentWrapper<T>
    {
        public ObjectId Id { get; private set; }
        public T Value { get; private set; }
    
        public DocumentWrapper(T value)
        {
            Id = ObjectId.GenerateNewId();
            Value = value;
        }
    }
    

    本机查询

    至于您的查询,它实际上并不等同于 Linq 查询。那将是:

    { 
        Dates : 
        {
            $elemMatch :
            { 
                $gt: ISODate("1987-01-01"), 
                $lt: ISODate("1987-01-10") 
            } 
        }
    }
    

    更多关于 $elemMatch here

    【讨论】:

    • 谢谢。本机查询按我的预期工作。我将看看 DocumentWrapper。此外,是否可以在不使用 LINQ 的情况下使用 C# 驱动程序来获得我想要的,而不使用使用 Query 类方法(Query.ElemMatch、Query.And、Query.GT、Query.LT)和 Collection.Find() 的 DocumentWrapper?我试过了,但似乎无法正确格式化查询。再次感谢。
    • @Jim 我非常怀疑这一点。问题似乎出在服务器而不是 C# 驱动程序中,所以无论如何它都不应该工作。来自release notes:“当目标是可枚举的文档时,添加了对 Any 运算符的支持。这将生成 $elemMatch 查询。我们不支持可枚举原语的目标,因为 mongodb 服务器不支持这些目标。作为一旦服务器支持这一点,我们也会添加它。”
    • @l3arnon,感谢所有帮助。我能够从 C# 驱动程序中找到一种方法(参见原始帖子中的编辑)。它没有使用 LINQ 甚至查询方法,但它完成了工作。接下来我会看一下文档包装器的想法。
    • @l3arnon,只是想最后一次跟进。我尝试了您提出的将 DateTime 包装在文档包装器中的建议,效果很好。 LINQ 有效,Query 类方法有效,并且从 Mongo 命令行查询有效(尽管我确实必须将 $gt 和 $lt 块包装在另一个 Date: { } 块中。非常感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-11
    • 2013-06-22
    • 2014-05-21
    • 2021-09-24
    相关资源
    最近更新 更多