Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB text search with sorting in C#

I'm trying to implement the text search in MongoDB, in C#.

The documentation doesn't cover anything about how to sort the text search results in C#.

I have a list of tags, separated by spaces, to match.
If I provide a string like "Tag1 Tag2", I would like the results to be provided in the following order:

  1. Anything that has BOTH 'Tag1' AND 'Tag2', followed by
  2. Anything that has only 'Tag1', followed by
  3. Anything that has only 'Tag2'

I have been trying to piece together some code:

var F = Builders<MongoPost>.Filter.Text(Tags);
var S = Builders<MongoPost>.Sort.MetaTextScore("textScore");
return Mongo.Driver.Find(F).Sort(S).ToListAsync().Result;

But I get the following error:

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

No proper documentation for this error...

Then I found the following code on this site:

var pipeline = new List<BsonDocument>
{
    BsonSerializer.Deserialize<BsonDocument>("{ $match: { $text: { $search: \"" + Tags + "\" } } }"),
    BsonSerializer.Deserialize<BsonDocument>("{ $sort: { score: { $meta: \"textScore\" } } }")
};

var R = Mongo.Driver.AggregateAsync(new BsonDocumentStagePipelineDefinition<MongoPost, MongoPost>(pipeline)).Result;
return R.ToListAsync().Result;

At least is runs without error, but I am trying to make the code in the style at the top, like you can do for the rest of the API, and not having to go back to console style text strings that have to be parsed for every execution. Also, I need to add a lot more criteria to the search, so this syntax is not practical for me.

Is there any proper documentation I missed? If not, does anyone know how to implement this in the style at the top?

like image 924
Thomas Avatar asked Aug 25 '15 01:08

Thomas


2 Answers

Using this post: Retrieve Relevance Ordered Result from Text Query on MongoDB Collection using the C# Driver

I got it to work with the following code:

var F = Builders<MongoPost>.Filter.Text(Tags);
var P = Builders<MongoPost>.Projection.MetaTextScore("TextMatchScore");
var S = Builders<MongoPost>.Sort.MetaTextScore("TextMatchScore");
return Mongo.Driver.Find(F).Project<MongoPost>(P).Sort(S).ToListAsync().Result;

And my class MongoPost has the following field:

[BsonIgnoreIfNull]
public double? TextMatchScore { get; set; }
like image 84
Thomas Avatar answered Sep 23 '22 01:09

Thomas


Alternatively you could add convention pack to ignore extra elements for all models or only for this particular one and then you won't need to add extra property:

ConventionRegistry.Register("conventions", new ConventionPack
{
    new IgnoreExtraElementsConvention(true)
}, type => true));

And then the pipeline will look as follows:

var sortedResult = await collection
    .Find(Builders<Model>.Filter.Text(searchTerm))
    .Project<Model>(Builders<Model>.Projection.MetaTextScore("score"))
    .Sort(Builders<Model>.Sort.MetaTextScore("score"))
    .ToListAsync();
like image 33
Andrii Litvinov Avatar answered Sep 27 '22 01:09

Andrii Litvinov