Azure Search SDK fails partial search when using special characters - c#

I have a below code which should return all the names starting with T&S from azure index
for example the results should be like below
T&S
T&S Limited
T&S Corporation
The search text we see in the code is the UrlEncoded version of "T&S*"
Search Code Block
var response = await _searchClient.Documents.SearchAsync<customDto>("%22T%26S%22*",
new SearchParameters
{
SearchFields = new List<string> { "Name" },
SearchMode = SearchMode.All
});
Custom DTO
public class CustomDto{
public CustomDto(int id,string name)
{
Id=Convert.ToString(id),
Name=name
}
[IsSearchable, IsFilterable]
[System.ComponentModel.DataAnnotations.Key]
public string Id { get; }
[IsSearchable, IsFilterable, IsSortable]
public string Name {get;}
}
Now,
If i put the similar search text on the azure search query window
i get results as expected %22T%26S%22*&searchMode=all&searchFields=Name
But for some reason the code returns empty result. I dont get what am i doing wrong here.
Please assist.
Thank you

Can you try with the following code. This uses Microsoft.Azure.Search SDK (version 10.1.0).
var searchCredentials = new SearchCredentials("<api-key (admin or query>");
var indexClient = new SearchIndexClient("<search-service-name>", "<index-name>", searchCredentials);
var results = indexClient.Documents.Search("\"T\\&S\"*",
new SearchParameters
{
SearchFields = new List<string> { "Name" },
SearchMode = SearchMode.All
});
SDK actually makes a POST request so you don't really have to URL encode the search string (you would need to do that when you issue a GET request). What you need to do is escape & character by prefixing it with a \ and that's what I did. Please see Escaping Special Characters here: https://learn.microsoft.com/en-us/azure/search/query-lucene-syntax#bkmk_syntax for more information.

Related

Custom Type Deserialize to 'object' results in extra curly brackets

I have a class like this:
class Document
{
public string id {get;set;}
public string name {get;set;}
}
var doc = new Document() { id= "1", name = "Doc1" };
var docs = new List<Document>() { doc };
...
I am sending docs through HTTP post to my backend service.
In my controller, I am receiving it as List docs.
The reason for this is, I want to use one controller to accept any Custom Types.
The trouble is, that, each item in List docs are wrapped inside extra curly braces
"{{\"id\":\"1\", \"name\":\"Doc1\"}}"
How to prevent extra curly braces? Can it be prevented?
If cannot be prevented, is there a way to extra the object removing the extra "{" and "}" ?
There is something called Newtonsoft.Json. It has whole lot of methods like Serialize and Deserialize objects. Check the link. Specially when you are sending values to the Api, The Api should accept the same thing as you are sending. If you are sending List then Api should accept same class List in its parameter.
If you are receiving some result from the API/you are consuming the API you should deserialize the string received in result.
To do this you can use -:
JObject,JToken
Newtonsoft.Json.JsonConvert.Deserialize
The actual output of serializing the list of documents would be:
[{"id":"1","name":"Doc1"}]
The outer [] is an array, and each object inside {id: number, name: string} is your document.
var doc = new Document { id = "1", name = "Doc1" };
var docs = new List<Document> { doc };
var result = Newtonsoft.Json.JsonConvert.SerializeObject(docs);
Console.WriteLine(result);

Neo4jClient: Merge with OnCreate leads to broken Cypher Query

We are using Neo4j Community 3.2.2 with Neo4jClient 2.0.0.9 and try to create a node if it does not exist. This is covered in the cypher examples and questions like this here on SO, so we thought that should be pretty straight forward:
public class KlientNode
{
[JsonProperty(PropertyName = "id")]
public Guid Id { get; set; }
}
and:
var neuerKlient = new KlientNode { Id = ev.KlientId };
var kq = graphClient.Cypher
.Merge("(klient:Klient { id: {klientId} })")
.OnCreate()
.Set("klient = {neuerKlient}").WithParams(new
{
klientId = neuerKlient.Id,
neuerKlient
});
Console.WriteLine(kq.Query.DebugQueryText);
kq.ExecuteWithoutResults();
So we basically copied the example 1:1.
Unfortunately, this leads to the following output and exception:
MERGE (klient:Klient { id: "80248429-ea80-4a5d-8d4e-88dc1499ea8a" })
ON CREATE
SET klient = {
"id": "80248429-ea80-4a5d-8d4e-88dc1499ea8a"
}
Neo4jClient.NeoException: SyntaxError: Invalid input 'N': expected 'p/P' (line 5, column 2 (offset: 250))
"ON CREATE"
^
The cause seem to be the quotes around the "id" in the SET klient = ... query. If I paste the generated query to the neo4j web console, it shows a syntax error, and if I remove the quotation marks, the query runs just fine.
Anyone has an idea what might be causing the broken query when we just seem to copy the examples almost verbatim?
FWIW, I'm not sure why that happened, but we were able to solve it as follows:
var kq = graphClient.Cypher
.Merge($"(klient:{NodeName} {{ id: {{klientId}} }})")
.OnCreate()
.Set("klient = {neuerKlient}").WithParams(new
{
klientId = ev.KlientId,
neuerKlient = new KlientNode
{
Id = ev.KlientId,
},
});
Console.WriteLine(kq.Query.DebugQueryText);
kq.ExecuteWithoutResults();
The debug output is still the same (ignore the different random Guid):
MERGE (klient:Klient { id: "87798b47-ab1b-49b7-9c5e-018cd244465e" })
ON CREATE
SET klient = {
"id": "87798b47-ab1b-49b7-9c5e-018cd244465e"
}
and the query is still broken if I try to paste it into the neo4j web frontend:
However, the query now executes without an exception.
Since the only change is the shorthand definition of the neuerKlient property in the anonymous object, I assume there is some behaviour internally that was causing the error (even though the debug query output was the same).

MVVM get data from text file

I can not get the logic how to search in a text file and then get the data I need using model view view model.
Basically, I have to make a dictionary app and I have word,language and description in the text file. Like:
cat;e English; it is a four leg animal
In the model I have a text box where the client writes a word and two other boxes, where language and description of the word should be shown.
I just can not get how to search in this file. I tried to search online but nothing seemed to meet my exact question.
Unless your file is going to change you can get away with reading the entire file up front when running your application and putting the data into lists of models for your view models.
As this is essentially a CSV file, and assuming each entry is a line, using a Semi-colon as the delimiter we can use the .Net CSV parser to process your file into your models:
Basic Model:
public class DictionaryEntryModel {
public string Word { get; set; }
public string Language { get; set; }
public string Description { get; set; }
}
Example view model with a constructor to fill out your models:
public class DictionaryViewModel {
// This will be a INotify based property in your VM
public List<DictionaryEntryModel> DictionaryEntries { get; set; }
public DictionaryViewModel () {
DictionaryEntries = new List<DictionaryEntryModel>();
// Create a parser with the [;] delimiter
var textFieldParser = new TextFieldParser(new StringReader(File.ReadAllText(filePath)))
{
Delimiters = new string[] { ";" }
};
while (!textFieldParser.EndOfData)
{
var entry = textFieldParser.ReadFields();
DictionaryEntries.Add(new DictionaryEntryModel()
{
Word = entry[0],
Language = entry[1],
Description = entry[2]
});
}
// Don't forget to close!
textFieldParser.Close();
}
}
You can now bind your view using the property DictionaryEntries and as long as your app is open it will preserve your full file as the list of DictionaryEntryModel.
Hope this helps!
I'm not addressing the MVVM part here, but just how to search the text file in order to get resulting data according to a search term, using case insensitive regex.
string dictionaryFileName = #"C:\Test\SampleDictionary.txt"; // replace with your file path
string searchedTerm = "Cat"; // Replace with user input word
string searchRegex = string.Format("^(?<Term>{0});(?<Lang>[^;]*);(?<Desc>.*)$", searchedTerm);
string foundTerm;
string foundLanguage;
string foundDescription;
using (var s = new StreamReader(dictionaryFileName, Encoding.UTF8))
{
string line;
while ((line = s.ReadLine()) != null)
{
var matches = Regex.Match(line, searchRegex, RegexOptions.IgnoreCase);
if (matches.Success)
{
foundTerm = matches.Groups["Term"].Value;
foundLanguage = matches.Groups["Lang"].Value;
foundDescription = matches.Groups["Desc"].Value;
break;
}
}
}
Then you can display the resulting strings to the user.
Note that this will work for typical input words, but it might produce strange results if the user inputs special characters that interfere with the regular expression syntax. Most of this might be corrected by utilizing Regex.Escape(searchedTerm).

Full text search in mongodb in .net

I have to search contents in all documents in particular collection of mongodb in .net mvc . I have tried with mongodb shell by creating index successfully like here .
db.collection_name.createIndex( { subject: "text" } )
db.collection_name.find( { $text: { $search: "search_word" } } )
It works fine . but when i put it in .net that gives me error . I googled it and got following solution for indexing .
collection.EnsureIndex(new IndexKeysBuilder().Ascending("subject"));
now how can i run this query db.collection_name.find( { $text: { $search: "coffee" } } ) .
I am trying in .net as following way .
collection.CreateIndex("subject":"text");
var query = collection.Find({ $text: { $search: "coffe" }});
but I am getting error on first line "represents text as series of unicode ....syntax error "
2nd line error "There is no argument given that corresponds to required formal parameters " And "unexpected character $ ".
any suggestion will be appreciated .
I could create text indexes with this command:
collection.Indexes.CreateOne(Builders<searchFileByAuthor>.IndexKeys.Text(x=>x.subject));
And than i could query index this way:
collection.Find(Builders<searchFileByAuthor>.Filter.Text("coffe")).ToList();
searchFileByAuthor is just my fake class with subject field:
public class searchFileByAuthor
{
public int Id { get; set; }
public string subject { get; set; }
}
Maksim Simkin answer is correct, althought it is obsolete.
The updated version would be:
collection.Indexes.CreateOne(new CreateIndexModel<YourClass>(Builders<YourClass>.IndexKeys.Text(x => x.something)));
or, if you would like to use the Wildcard Indexing (to index the entire document), you could do like this:
collection.Indexes.CreateOne(new CreateIndexModel<YourClass>(Builders<YourClass>.IndexKeys.Text("$**")));
or maybe you want/have more indexes for some reason, than do this:
var indexWildcardTextSearch = new CreateIndexModel<YourClass>(Builders<YourClass>.IndexKeys.Text("$**"));
List<CreateIndexModel<YourClass>> indexes = new List<CreateIndexModel<YourClass>>();
indexes.Add(indexWildcardTextSearch);
collection.Indexes.CreateMany(indexes);
And to query, it remains the same:
collection.Find(Builders<YourClass>.Filter.Text("something")).ToList();
public List<T> FindSearch<T>(string collectionName, string searchWord) {
IMongoQuery query = Query.Text(searchWord);
List<T> find = getCollection<T>(collectionName).Find(query).ToList();
return find;
}

Specifying and using a NGramTokenizer with the C# NEST client for Elastic Search

Updated to show a working sample
I am trying to do a partial search on a collection of usernames in ElasticSearch.
Searching around has pointed me in the nGram Tokenizer direction but I am stumped at proper implementation and fail to get any results.
This is the the relevant code stripped from the project I'm working on.
I have tried different combinations and search types to no avail.
setup.cs
var client = new ElasticClient(settings.ConnectionSettings);
// (Try and) Setup the nGram tokenizer.
var indexSettings = new IndexSettings();
var custonAnalyzer = new CustomAnalyzer();
customAnalyzer.Tokenizer = "mynGram";
customAnalyzer.Filter = new List<string> { "lowercase" };
indexSettings.Analysis.Analyzers.Add("mynGram", customAnalyzer);
indexSettings.Analysis.Tokenizers.Add("mynGram", new NGramTokenizer
{
MaxGram = 10,
MinGram = 2
});
client.CreateIndex(settings.ConnectionSettings.DefaultIndex, indexSettings);
client.MapFromAttributes<Profile>();
// Create and add a new profile object.
var profile = new Profile
{
Id = "1",
Username = "Russell"
};
client.IndexAsync(profile);
// Do search for object
var s = new SearchDescriptor<Profile>().Query(t => t.Term(c => c.Username, "russ"));
var results = client.Search<Profile>(s);
Profile.cs
public class Profile
{
public string Id { get; set; }
[ElasticProperty(IndexAnalyzer = "mynGram")]
public string Username { get; set; }
}
Any tips would be much appreciated.
Take a look at this from the es docs on nGram token filters:
"settings" : {
"analysis" : {
"analyzer" : {
"my_ngram_analyzer" : {
"tokenizer" : "my_ngram_tokenizer"
}
},
"tokenizer" : {
"my_ngram_tokenizer" : {
"type" : "nGram",
"min_gram" : "2",
"max_gram" : "3",
"token_chars": [ "letter", "digit" ]
}
}
}
}
A few things to note
You need to add mynGram to your analyzer or it won't be used. They way it works is like this. Each indexed field has an analyzer applied to it, an analyzer is one tokenizer followed by zero or more token filters. You have defined a nice nGram tokenizer (mynGram) to use, but you did not use it in customAnalyzer, it is using the standard tokenizer. (Basically you are just defining but never using mynGram.)
You need to tell elasticsearch to use your customAnalyzer in your mapping:
"properties": {"string_field": {"type": "string", "index_analyzer": customAnalyzer" }}
You should change the maxGram to a bigger number (maybe 10), otherwise 4 letter searches will not behave exactly as autocomplete (or could return nothing, depends on the search-time analyzer).
Use the _analyze api endpoint to test your analyzer. Something line this should work.
curl -XGET 'http://yourserver.com:9200?index_name/_analyze?analyzer=customAnalyzer' -d 'rlewis'
Good luck!

Categories