MongoDB文本搜索与投影

本文关键字:投影 搜索 文本 MongoDB | 更新日期: 2023-09-27 18:10:31

使用MongoDB与c#和驱动程序2.0,我试图做以下事情:

  • 文本搜索
  • 按文本搜索分数排序
  • 项目BigClass到SmallClass

下面是这些类的(简化版本):

class BigClass
{
    [BsonIgnoreIfDefault]
    public ObjectId             _id                 { get; set; }
    public string               Guid                { get; set; }
    public string               Title               { get; set; }
    public DateTime             CreationTime        { get; set; }
    // lots of other stuff
    [BsonIgnoreIfNull]
    public double?              TextMatchScore      { get; set; }       // Temporary place for the text match score, for sorting
}
class SmallClass
{
    [BsonIgnoreIfDefault]
    public ObjectId             _id                 { get; set; }
    public string               Title               { get; set; }
    [BsonIgnoreIfNull]
    public double?              TextMatchScore      { get; set; }       // Temporary place for the text match score, for sorting
}

如果我做一个文本搜索,它是非常简单的:

var F = Builders<BigClass>.Filter.Text("text I am looking for");
var Result = MongoDriver.Find(F).ToListAsync().Result;


如果我想按文本搜索的分数排序,那就有点混乱了(而且文档记录很差):

var F = Builders<BigClass>.Filter.Text("text I am looking for");
var P = Builders<BigClass>.Projection.MetaTextScore("TextMatchScore");
var S = Builders<BigClass>.Sort.MetaTextScore("TextMatchScore");
var Result = MongoDriver.Find(F).Project<BigClass>.Sort(S).ToListAsync().Result;

本质上它需要我在类(TextMatchScore)中添加一个字段来保存结果。


如果我想获得数据,而不需要排序并将其投影到SmallClass,这很简单:

var F = Builders<BigClass>.Filter.Text("text I am looking for");
var P = Builders<BigClass>.Projection.Include(_ => _.id).Include(_ => _.Title);
var Result = MongoDriver.Find(F).Project<SmallClass>(P).ToListAsync().Result;


现在,如果"I want it all",问题就出现了:

var F = Builders<BigClass>.Filter.Text("text I am looking for");
var P = Builders<BigClass>.Projection.MetaTextScore("TextMatchScore").Include(_ => _.id).Include(_ => _.Title).Include(_ => _.TextMatchScore);
var S = Builders<BigClass>.Sort.MetaTextScore("TextMatchScore");
var Result = MongoDriver.Find(F).Project<SmallClass>.Sort(S).ToListAsync().Result;

我得到一个异常:

Message = "QueryFailure flag was true (response was { '"$err'" : '"Can't canonicalize query: BadValue must have $meta projection for all $meta sort keys'", '"code'" : 17287 })."

正如预期的那样,这个错误没有记录在任何地方,因为Mongo的家伙希望用户自己记录一切。

如果我做投影到'BigClass',没有问题,代码运行,只是填充正确的字段。

如果你用c#搜索文本,你找到的帖子是我的,当我试图弄清楚文本搜索,这也是很差的文档。

所以当我们把投影、文本搜索和排序结合起来时,似乎没有任何例子,我就是不能让它工作。

有谁知道这个问题的原因吗?

MongoDB文本搜索与投影

这个适合我:

var client = new MongoClient();
var db = client.GetDatabase("test");
var col = db.GetCollection<BigClass>("big");
await db.DropCollectionAsync(col.CollectionNamespace.CollectionName);
await col.Indexes.CreateOneAsync(Builders<BigClass>.IndexKeys.Text(x => x.Title));
await col.InsertManyAsync(new[]
{
    new BigClass { Title = "One Jumped Over The Moon" },
    new BigClass { Title = "Two went Jumping Over The Sun" }
});

var filter = Builders<BigClass>.Filter.Text("Jump Over");
// don't need to Include(x => x.TextMatchScore) because it's already been included with MetaTextScore.
var projection = Builders<BigClass>.Projection.MetaTextScore("TextMatchScore").Include(x => x._id).Include(x => x.Title);
var sort = Builders<BigClass>.Sort.MetaTextScore("TextMatchScore");
var result = await col.Find(filter).Project<SmallClass>(projection).Sort(sort).ToListAsync();

我删除了TextMatchScore的包含。它仍然会回来,因为它被包含在MetaTextScore("TextMatchScore")中。

文档是一个正在进行的工作。我们首先处理主要的用例,因为这些用例影响最多的人。这个用例并不常见,也没有被记录。我们当然接受拉取请求,包括代码和文档。另外,您也可以在jira.mongodb.org上提交CSHARP项目下的文档申请。

解决方案在MongoDB中工作。司机2。X如下。重要的是不要做Include in Projection,因为它会擦除默认的,(或者记得添加适当的投影)

查询:

 {
   "find":"SoceCollection",
   "filter":{
      "$text":{
         "$search":"some text to search"
      }
   },
   "sort":{
      "TextScore":{
         "$meta":"textScore"
      }
   },
   "projection":{
      "TextScore":{
         "$meta":"textScore"
      },
      "_id":0,
      "CreatedDate":0
   },
   "limit":20,
   "collation":{
      "locale":"en",
      "strength":1
   } ...

代码
var sort = Builders<BigModel>.Sort.MetaTextScore(nameof(LightModel.TextScore));
            var projection = Builders<BigModel>.Projection
            .MetaTextScore(nameof(LightModel.TextScore))
                    .Exclude(x => x.Id)
                    .Exclude(x => x.CreatedDate); 
 
            return await Collection()
               .Find(filter, new FindOptions { Collation = new Collation("en", strength: CollationStrength.Primary) })
                .Project<LightModel>(projection)
                .Sort(sort)
                .Limit(20)
                .ToListAsync();