Searching ElasticSearch using NEST C# Client - c#

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

Related

Convert foreach to optimised code using LINQ or any other way

This given line of code. How can I write in a more optimized way? The below code is working fine but I need another way of writing this
var attachments = new List<Attachments>();
foreach (var attachment in msg.attachments) //msg.attachments is a Dictionary<string, FileAttachment> type
{
var attach = new Attachments();
attach.name = attachment.Value.name;
attach.type = attachment.Value.type;
attachments.Add(attach);
}
This given line of code. How can I write in a more optimized way?
You mention LINQ, but LINQ is often the opposite of "optimized" - it is trading away efficiency to gain convenience and (sometimes) terseness. There is absolutely nothing wrong with your existing code. The only significant tweak would be to initialize the target list with the known length:
var attachments = new List<Attachments>(msg.attachments.Count);
You could also do things such as using object initializer syntax on the new Attachments, or avoiding the multiple .Value, but to be honest: neither of those is likely to make an even remotely measurable difference. But:
foreach (var attachment in msg.attachments.Values)
{
attachments.Add(new Attachments {
name = attachment.name, type = attachment.type
});
}
Is this what you want?
var attachments =
msg
.attachments
.Select(attachment => new Attachments()
{
name = attachment.Value.name,
type = attachment.Value.type,
})
.ToList();
This should do the trick:
var attachments = msg.attachments
.Select(a => new Attachments
{
name = a.Value.name,
type = a.Value.type
})
.ToList();
But as others have pointed out in the comments, this isn't really more optimised (although a bit more readable in my opinion).
Don't forget the linq query syntax:
var attachments = from att in msg.attachments
select new Attachments { name = att.Value.name, type = att.Value.type };
If you use it like this, you will be able to make use of the deferred execution.
If you really need to collapse it into a list, you can just call .ToList() on it.
In addition to using a LINQ lambda syntax, you can use this comprehension query:
var attachments = ( from attachment in msg.attachments
select new Attachments
{
name = attachment.Value.name,
type = attachment.Value.type
}
).ToList();
It select a new object instance from every items in the source collection and takes a List of the resulting query.
But for such a simple piece of code, there shouldn't be much difference, in very little gain or loss (can be benchmarked).
Thus it's just a matter of preference for clean and maintainable code.

How to create an anonymous type using codedom

Essentially I am aiming to get something like the following code as output from my codedom:
var foo = new { variable1 = "value", variable2 = 5 };
I won't just be using it for a variable assignment but I wanted a simple example of how to create an anonymous type. I'm not sure that this exact code is possible to achieve but if there's some kind of work around you could point me in the direction of I would be much obliged.
I've had a look at the CodeObjectCreateExpression and I don't think that's going to do what I'm looking for and I can't find anything else that comes close.
Thanks in advance for all and any help.
CodeDOM attempts to be language-neutral and hasn't been updated for a very long time, so I don't think you can use any of its built-in types to do this. But you can use CodeSnippetExpression to inject this code as string:
var expression = new CodeSnippetExpression("new { variable1 = \"value\", variable2 = 5 }");
var statement = new CodeVariableDeclarationStatement("var", "foo", expression);

How can you filter out parts in MEF2?

I'm trying to port some code from targeting the .NET Framework to .NET Core, and part of this involves switching from MEF1 to MEF2. There doesn't seem to be a great deal of documentation on using MEF in .NET Core, and I can't find any examples demonstrating how to filter out parts as was possible with MEF1.
In my original code (using MEF1), I wanted to load all parts exported from a set of assemblies except for MockCommunicationService. I implemented this as follows:
// Filter out invalid exports.
Func<ComposablePartDefinition, bool> partFilter = it => !it.ToString().Contains(nameof(MockCommunicationService));
var assemblyPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var catalog = new DirectoryCatalog(assemblyPath).Filter(partFilter);
var container = new CompositionContainer(catalog);
What would the equivalent to this be in MEF2? I imagine that it probably involves using ConventionBuilder, but I don't know if there's a way to define a convention along the lines of "permit everything except x".
Ideally, something like this would be great:
var conventions = new ConventionBuilder();
conventions.ForType<MockCommunicationService>().SuppressExports();
var configuration = new ContainerConfiguration()
.WithAssemblies(assemblies, conventions);
It's hardly an optimal solution, but this is the workaround I've used for the time being.
Looking at the source for ContainerConfiguration, I see that WithAssemblies is defined as:
public ContainerConfiguration WithAssemblies(IEnumerable<Assembly> assemblies, AttributedModelProvider conventions)
{
if (assemblies == null) throw new ArgumentNullException(nameof(assemblies));
return WithParts(assemblies.SelectMany(a => a.DefinedTypes.Select(dt => dt.AsType())), conventions);
}
So instead of using WithAssemblies, I use WithParts as follows:
// Filter out invalid exports.
Predicate<Type> filterParts = part => !part.Equals(typeof(MockCommunicationService));
var parts = from name in DependencyContext.Default.GetDefaultAssemblyNames()
where name.Name.StartsWith("<<Company>>")
let assembly = Assembly.Load(name)
from definedType in assembly.DefinedTypes
let part = definedType.AsType()
where filterParts(part)
select part;
var configuration = new ContainerConfiguration()
.WithParts(parts);
return configuration.CreateContainer();
Again, this seems more like a hacky workaround than a proper way to go about it, so I'm not going to accept this answer. If no other answers get posted this may be useful to others, though.

Mongo C# driver - Contains Filter

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));

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.

Categories