Mongo C# driver - Contains Filter - c#

I am using the latest version of Mongo C# driver which uses a lot of Async and builder pattern. Which is nice. I am trying to convert SQL where clauses into Mongo FilterDefinition object.
Any idea how to handle "contains"?
like:
where x contains 'ABC'

In order to achieve that in V2 API, use the `Filter.Regex':
var collection = db.GetCollection<BsonDocument>("collection");
var filter = Builders<BsonDocument>.Filter.Regex("fieldName", new BsonRegularExpression(".*fieldValue.*"));
var data = await (await coll.FindAsync<BsonDocument>(filter).ConfigureAwait(false)).ToListAsync();
//continue process data

If x is a string, you could do so with a simple regex. For the 2.0 driver, you can manually create the FilterDefinition:
FilterDefinition<BsonDocument> filter = "{ x : { $regex : /ABC/ } }";
Or build the filter use the Builder:
var builder = Builders<BsonDocument>.Filter;
var filter = builder.Matches("x", "ABC");
Then you can use the filter in your query:
using (var cursor = await collection.Find(filter).ToCursorAsync())
{
// ...
}

I was able to get this working using Filter.AnyIn like so
var filter = Builders<BsonDocument>.Filter.AnyIn("x", new List<string> { "ABC" });
This works if you're looking for multiple values too, just add them to the list.

First, I highly recommend taking MongoDB University's .NET course (from Mongo itself). It's really thorough, and covers your question (and more) in depth.
Second, I assume that x is an array in your example.
MongoDB correctly handles polymorphism with arrays. If you have a class Post with an array of Tags, you can filter where Tag = ABC.
If you're using the C# linq methods, that looks like .Find(p => p.Tags == "ABC"). If you're using BsonDocument, that looks like new BsonDocument().Add("Tags", "ABC").

I have another way which I don't love but it works. The answer that is marked correct is half wrong (Matches is a method of Builders). In this example the / act like a % in a sql query LIKE statement. I'm still looking for a better way and will update if I find one that is more Equals filter below.
List<yourobject> someList = await collection.Find("{ x: /Test/ }").ToListAsync();
var filter = Builders<yourobject>.Filter.Eq("x", "ABC");
List<yourobject> someList = await collection.Find(filter).ToListAsync();

If you want to just search input text you need to replace regex special characters.
Regex.Escape will ensure that these characters are processed literally rather than as metacharacters. Otherwise input text can be used to query regex patterns which is probably not what is required.
var text = "ABC";
var filter = Builders<BsonDocument>.Filter.Regex("x", BsonRegularExpression.Create(Regex.Escape(text)));
If you need case insensitive check. Then you can pass case insensitive regex to BsonRegularExpression.Create:
var text = "ABC";
var escapeText = Regex.Escape(text);
var regex = new Regex(escapeText, RegexOptions.IgnoreCase);
var filter = Builders<BsonDocument>.Filter.Regex("x", BsonRegularExpression.Create(regex));

Related

Mongodb query fails with FilterDefinition in a Collection using c#

I'm trying to move this "simple" query from MongoDB Console to my C# Code.
This is the query:
db.getCollection('Entity').find({ Keywords : { $regex : /ABC/ } })
This query returns all Entities that have a keyword that contains 'ABC' in the array of keyowords.
When I try to do that in C# using the MongoDB C# Driver, I code that:
FilterDefinition<Entity> filterDefinition = "{ Keywords : { $regex : /ABC/ } }";
var data= await _repository.Collection.Find(filterDefinition).ToListAsync();
But I have an error:
FormatException: JSON reader expected a string but found '/ABC/'.
Thanks!!
You can use an expression that contains a Regex like this
var regex = new Regex("ABC");
return collection.Find(e => regex.IsMatch(e.Keywords)).ToListAsync();
If you want to create a FilterDefinition use the builder
var filter = Builders<Entity>.Filter.Regex("keywords", new BsonRegularExpression("ABC"));
return collection.Find(filter).ToListAsync();
If you want to find matches in an array use an expression like
return collection.Find(e => e.Keywords.Any(k => regex.IsMatch(k)).ToListAsync();
Have you tried using the Matches method in the csharp driver? You can use the Mongo query builders to construct your query to run against your collection.
e.g.
var search = "ABC";
Query<Entity>.Matches(t => t.Keywords, new BsonRegularExpression(string.Format("/{0}/i", Regex.Escape(search))));
The returning IMongoQuery can then be used to query your collection along with other queries.

Get All 'documents' from MongoDB 'collection'

I need to retrieve all the documents that are in my collection in MongoDB, but I cannot figure out how. I have declared my 'collection' like this-
private static IMongoCollection<Project> SpeCollection = db.GetCollection<Project>("collection_Project");
And I followed what is explained in this MongoDB tutorial. I adjusted it for my needs, like-
var documents = await SpeCollection.Find(new Project()).ToListAsync();
However, I keep having the following error-
MongoDB.Driver.IMongoCollection does not have a definition for 'Find' and the best override of the extension method [superlong stuff]. Find contains non valid arguments.
Using the current version of the driver (v2.0) you can do that by passing a filter that matches everything:
var documents = await SpeCollection.Find(_ => true).ToListAsync();
They have also added an empty filter (FilterDefinition.Empty) which will arrive in the next version of the driver (v2.1):
var documents = await SpeCollection.Find(Builders<Project>.Filter.Empty).ToListAsync();
Simplest Way
Retrieve all the documents-
var documents = SpeCollection.AsQueryable();
Also convert to JSON object-
var json = Json(documents, JsonRequestBehavior.AllowGet);
If you want all documents, why not use Find all?
var documents = await SpeCollection.Find(new BsonDocument()).ToListAsync();

C# equivalent of Lambda

I'm trying to convert some JS into C# and i got to this piece but cant figure out what a C# equivalent would be. Hoping someone can point me in the right direction?
I just need help with the contents of these two functions. The $iterator is coded in another spot but im guessing that the C# version of the following code doesnt need it. If you need me to add it, i can.
The context these functions are being called is:
var centers = Lambda.array(Lambda.map(this.hexes,function(hex) {
return me.hexToCenter(hex);
}));
And the functions are:
var Lambda = function() { }
Lambda.array = function(it) {
var a = new Array();
var $it0 = $iterator(it)();
while( $it0.hasNext() ) {
var i = $it0.next();
a.push(i);
}
return a;
}
Lambda.map = function(it,f) {
var l = new List();
var $it0 = $iterator(it)();
while( $it0.hasNext() ) {
var x = $it0.next();
l.add(f(x));
}
return l;
}
You don't really need your own map and array methods. There is already the same functionality available, you just have to add using System.Linq; at the top of your file and you'll be able to use both Select, which is a projection method and ToArray which created an array from your collection. They are both extension methods set on IEnumerable<T>, so you can use them on almost any collection.
var centers = hexes.Select(x => me.hexToCenter(x)).ToArray();
is an equivalent of you JavaScript code:
var centers = Lambda.array(Lambda.map(this.hexes,function(hex) {
return me.hexToCenter(hex);
}));
This looks like a fairly simple C# lambda:
var centers = this.hexes.Select(hex => me.hexToCenter(hex)).ToList();
Select and ToList extension methods are provided by LINQ - you need to add using System.Linq to use them.
You probably want to go the LINQ route here.
Your map is equivalent to LINQ's Select, e.g.
var centers = this.hexes.Select(hex => me.hexToCenter(hex)).ToArray();
The expression hex => me.hexToCenter(hex) is a lambda expression in C#, which Select uses to project this.hexes into your desired form.
ToArray() is equivalent to your Lambda.array call.
101 LINQ Samples in C# is a great resource for examples on using LINQ.
note most of the 101 samples use the query syntax as opposed to the functional syntax I've used above. They are roughly equivalent for simple cases, but being comfortable with the functional syntax shouldn't be a problem for you, coming from JS background.

Searching ElasticSearch using NEST C# Client

I started looking around for a search engine and after some reading I decided going with ElasticSearch (which is quite amazing :)), my project is in C# so I looked around for a client and started using NEST, everything is quite straightforward but I am a bit confused on the searching part.
I want to search all fields on a specific type what I came up with is the following code:
elasticClient.Search<NewType>(s => s.Query(q => q.QueryString(d => d.Query(queryString))));
I saw that much of the string query search is deprecated and wanted to make sure the above is the correct way of doing this (the above is not marked as deprecated...) also it is a bit long for a simple task so maybe anyone knows another way of doing this.
Thanks
I just use the string query version: create my query object using C# anonymous type and serialize it to JSON.
That way, I can have straightforward mapping from all the JSON query examples out there, no need translating into this "query DSL".
Elasticsearch by itself evolves quite rapidly, and this query DSL thus is bound to lack some features.
Edit: Example:
var query = "blabla";
var q = new
{
query = new
{
text = new
{
_all= query
}
},
from = (page-1)*pageSize,
size=pageSize
};
var qJson = JsonConvert.SerializeObject(q);
var hits = _elasticClient.Search<SearchItem>(qJson);
Just to confirm
elasticClient.Search<NewType>(s => s.Query(q => q.QueryString(d => d.Query(queryString))));
Is the preferred way to search and the fact it feels a bit long is because there are alot of options you can play with that are not used here. I'm always open on suggestions to make it shorter!
The string overload is deprecated but wont be removed from NEST. I'll update the obsolete message to explicitly mention this.
If the anonymous types above aren't your thing, you can just use JObjects from json.net and build your query that way. Then you can run it the same way above.
JObject query = new JObject();
query["query"] = new JObject();
query["query"]["text"] = new JObject();
query["query"]["text"]["_all"] = searchTerm;
query["from"] = start;
query["size"] = maxResults;
string stringQuery = JsonConvert.SerializeObject(query);
var results = connectedClient.SearchRaw<SearchItem>(stringQuery);
I like this way better because the DSL in ES uses reserved keywords in C#, like bool, so I don't have to do any escaping.
With ElasticSearch 2.0, I have to use a SearchResponse< NewType > in the Search method like this :
var json = JsonConvert.SerializeObject(searchQuery);
var body = new PostData<object>(json);
var res = _elasticClient.Search<SearchResponse<NewType>>(body);
IEnumerable<NewType> result = res.Body.Hits.Select(h => h.Source).ToList();
Hope it help.
Note: I found very few documentation about PostData class and its generic type parameter

Find an exact word in a string

How do i find out if a string is containing the exact word i am looking for?
example: "this is my text"; word looking for: "text"; found: yes.
example: "these are my texts"; word looking for: "text"; found: no.
it's inside a linq to entities query, so regex won't work?
Edit:
This is more or less what i'm doing now and i want to replace it by a function that returns only when it's the exact match.
using (Model.Manager ctx = new Model.Manager())
{
var result = from p in ctx.Companies where p.Name.Contains(workLookingFor) select p;
}
Solution so far:
I could use .Contains() on my DB and use RegEx on the results pulled from the DB. Since the exact matches are always inside the broader results from .Contains() (which i still need anyways) this could be a good solution
This works for me. It's not perfect but might help.
public static bool matchWholeWord(string test, string search)
{
var index = test.IndexOf(search);
if (index == -1)
return false;
var after = index + search.Length;
if (after == test.Length)
return true;
return !char.IsLetterOrDigit(test[after]);
}
in your code:
using (Model.Manager ctx = new Model.Manager())
{
var result = from p in ctx.Companies
where matchWholeWord(p.Name, workLookingFor)
select p;
}
There isn't an easy way. You have three options
run your query, then do the pattern matching on the client
use SQL Fulltext Search engine
add a CLR function to the database that lets you do regex matching
I had to use something similar to this to implement a search algorithm using LINQ recently. I found the MSDN article on combining LINQ and Regular Expressions to be useful, along with a regular expression that used the space marker to identify whitespace. By constructing the regex with my search parameters and combining that into my LINQ expression, I ended up with what I needed.

Categories