Search code examples
mongodb-.net-driver

Using a Regex against a simple list with `ElemMatch` in MongoDB .NET


Given a document with a list of strings, how would you assemble a C# MongoDB query to regex against each list item?

For example, here's some data.

{
  "_id": {
    "$oid": "4ded270ab29e220de8935c7b"
  },
  // ... some other stuff ...
  "categories": [
    {
      "Some Category",
      "Another Category",
      "Yet Another Category",
      "One Last Category"
    }
  ]
},

Ultimately, how would I structure a query like this that could be strongly-typed through the MonoDB LINQ provider?

{ "categories": { $elemMatch: { $regex: someSearch, $options: "i" } } }

I'm trying to make this work with ElemMatch, but I can't seem to structure a BsonRegularExpression to work with that method. If the data was a list of keyed elements, it looks like I could make this work for some key, say itemName.

// Doesn't translate to a list of raw strings.
Query.ElemMatch ("categories", Query.Match("itemName", new BsonRegularExpression (new Regex (someSearch, RegexOptions.IgnoreCase)));

As soon as I try to make that regex work directly on ElemMatch, though, I can't match the overloads.

// Doesn't compile: cannot convert BsonRegularExpress to IMongoQuery.
Query.ElemMatch ("categories", new BsonRegularExpression (new Regex (someSearch, RegexOptions.IgnoreCase)));

Is there some method for converting a BsonRegularExpression into an IMongoQuery object directly?

Or is there some Matches syntax for applying the current iterated element in a list that would allow for a hybrid of the two? Something like this made up code.

// Doesn't work: totally making this up.
Query.ElemMatch ("categories", Query.Matches (iteratorElement, new BsonRegularExpression (new Regex (someSearch, RegexOptions.IgnoreCase)));

I was hoping to avoid just sending a raw string into the MongoDB driver, both so I can escape the search string from injection and so the code isn't littered with magic string field names (instead limited to just the BsonElement attribute on the DB model fields).


Solution

  • This might not be 100% what you are after (as it's not IQueryable); but it does achieve what you want (strongly typed regex filtering of a collection, using Linq):

            var videosMongo = DbManager.Db.GetCollection<Video> ("videos");
            var videosCollection = videosMongo.AsQueryable<Video> ();
            videosMongo.Insert (new Video () {
                Id = new ObjectId (),
                Tags = new string[]{ "one", "two", "test three", "four test", "five" },
                Name = "video one"
            });
            videosMongo.Insert (new Video () {
                Id = new ObjectId (),
                Tags = new string[]{ "one", "two", "test three", "four test", "five" },
                Name = "video two"
            });
            videosMongo.Insert (new Video () {
                Id = new ObjectId (),
                Tags = new string[]{ "one", "two" },
                Name = "video three"
            });
            videosMongo.Insert (new Video () {
                Id = new ObjectId (),
                Tags = new string[]{ "a test" },
                Name = "video four"
            });
    
    
            var videos = videosCollection.Where (v => v.Name == "video four").ToList ();
    
            var collection = DbManager.Db.GetCollection<Video> ("videos");
            var regex = new BsonRegularExpression ("test");
            var query = Query<Video>.Matches (p => p.Tags, regex);
            var results = collection.Find (query).ToList ();
    

    I worked this out by using the excellent resource here : http://www.layerworks.com/blog/2014/11/11/mongodb-shell-csharp-driver-comparison-cheat-cheet