Mapping Nest response to a C# object - elasticsearch

I am trying to map a nest response to a c# object and having little luck.
My query returns the result from
var resp = Eclient.Search<dynamic>(q => q
.Type("movies")
.From(0)
.Size(20)
.QueryRaw(queryString));
How can I map the response to a C# object?
It needs to be dynamic, i.e there are arrays that wary in length in the response.

Serialize a dynamic object to JSON and then deserialize a string to a proper C# object. But that would be totally inefficient. Why not pass a dynamic object around or just do manual mapping?

Related

Is it possible to use ElasticSearch.Net or Nest for dynamic response

Is there a client.Read(...) without generics? I have found none, neither in Nest nor ElasticSearch.Net.
Version 1.5 has an IDocument that might solve my problem but I cannot use that version with Elasticsearch5.5.
All examples, version 5 and 6, of ElasticSearch.Net and Nest require me to know the format of the response as generic at compile time. E.g. Read<Customer>(...)
My problem is that the we do not know the format of the database and we don't know the format of the output; but it should all be configurable.
You can use dynamic as the generic type if the response is truly dynamic.
In 5.x, this will be Json.NET's JObject type under the covers (so you could use JObject instead if you prefer).
In 6.x, dynamic will also work but the actual type will be an internal JObject type. If you would prefer to work with Json.NET's JObject type, you can hook up Json.NET as the serializer using the NEST.JsonNetSerializer nuget package, to use as the serializer for your documents and then use its JObject type as per 5.x.
(Feels strange to answer my own question but I want to show the resulting code for future reference.)
var settings = new ConnectionSettings(new Uri(#"http://localnhost:9200"))
.DefaultIndex("myindex");
var client = new ElasticClient(settings);
var res = client.Search<dynamic>(s => s
.AllTypes());
var rows = res.Documents;
Assert.IsTrue(rows.Count >= 1);
dynamic row = res.Documents.First();
Assert.AreEqual("50.7031526", row.POSITION.lat.ToString()); // It is case sensitive.
Assert.AreEqual(50.7031526, (double)row.POSITION.lat); // Convert to type explicitly.

In Nest (Elasticsearch), how can I get the raw json mapping of an index?

I want to check the discrepancies between my current mapping (as in my C# code) and the mapping in the elasticsearch index.
With only:
var res = esClient.GetMapping<EsCompany>();
I get GetMappingResponse object in c#, I will have to compare field by field for equality. Even worse, each field has their own properties, I have to descend into those properties for further comparison.
In my application, I prefer obtaining the raw json of the mapping, and I can easily diff two json objects for equality.
I then tried this:
var res = esClient.Raw.IndicesGetMapping(myIndexName);
But when I read res.Response, I get an AmbiguousMatchException exception.
When you connect to Elasticsearch you can choose to expose the raw response like this:
var client = new ElasticClient(new ConnectionSettings().ExposeRawResponse());
Then you should be able to access the raw json via:
var json = res.ConnectionStatus.ResponseRaw;

Cannot index document

I have written some code using the Elasticsearch.Net & NEST client library that should index a document without using a POCO for mapping fields as I have many different documents.
Question 1) Is this correct way to do create an index, does the .AddMapping<string>(mapping => mapping.Dynamic(true)) create the mapping based on the document passed in?
var newIndex = client.CreateIndex(indexName, index => index
.NumberOfReplicas(replicas)
.NumberOfShards(shards)
.Settings(settings => settings
.Add("merge.policy.merge_factor", "10")
.Add("search.slowlog.threshold.fetch.warn", "1s")
)
.AddMapping<string>(mapping => mapping.Dynamic(true))
);
Question 2) Is this possible?
string document = "{\"name\": \"Mike\"}";
var newIndex = client.Index(document, indexSelector => indexSelector
.Index(indexName)
);
When I run code in "Question 2" it returns:
{"Unable to perform request: 'POST ' on any of the nodes after retrying 0 times."}
NEST only deals with typed objects in this case passing a string will cause it to index the document into /{indexName}/string/{id}.
Since it can't infer an id from string and you do not pass it one it will fail on that or on the fact that it can't serialize a string. I'll update the client to throw a better exception in this case.
If you want to index a document as string use the exposed Elasticsearch.NET client like so:
client.Raw.Index(indexName, typeName, id, stringJson);
If you want elasticsearch to come up with an id you can use
client.Raw.Index(indexName, type, stringJson);
client is the NESTclient and the Raw property is an Elasticsearch.Net client with the same connectionsettings.
Please note that I might rename Raw with LowLevel in the next beta update, still debating that.

Translate odata uri to expression

I'd like to use an action filter to translate an Odata uri to a Linq expression. I'm doing this because i'm using the resulting expression to query nonSQL line of business systems. In the WCF web api this was trivial because the translated query was appended as a property of the the request object, as such:
var query = (EnumerableQuery)request.Properties["queryToCompose"];
That seems to have disappeared. Are there any public api's i can use to accomplish this?
I've been trying something similiar.. While not perfect, you can grab the OData expressions directly from the query string and build the LINQ expression manually:
var queryParams = HttpUtility.ParseQueryString( ControllerContext.Request.RequestUri.Query );
var top = queryParams.Get( "$top" );
var skip = queryParams.Get( "$skip" );
var orderby = queryParams.Get( "$orderby" );
And the apply that directly to your IQueryable or whatever you're using for the filtering. Not nearly as useful, but its a start.
So as it turns out the query has changed keys in the request property collection. It also seems that the internal filter that parses the query runs after the custom filters and thus doesn't add the query value. To get the translated query, call the following inside the controller action.
(EnumerableQuery<T>)this.Request.Properties["MS_QueryKey"];
Check out Linq2Rest. It solves this problem.

Return Count from Netflix oData Service When the LINQ Count() Method Doesn't Work

Is there a way to use a LINQ expression to request a Count query from the Netflix oData service in Silverlight 4?
The Netflix documentation shows that you can return counts by appending $count to a request for a collection, but a URL like this:
http://netflix.cloudapp.net/Catalog/Genres/$count
Is not generated from an expression like this:
var count = (from g in catalog.Genres select g).Count();
The above code returns an error saying that the Count method is not supported. Is there a way to do this in LINQ, or do I just need to make WebClient request to get the value?
Count and LongCount are not supported in Silverligth because they require a synchornous execution of the query. Since Silverlight requires all network operations to by asynchronous this is not possible.
You can either issue the HTTP query in question programatically not using DataServiceContext (or related classes), since the $count returns a text representation of the number, parsing the response is not that hard.
Or you can use a bit of a trick. You can use IncludeTotalCount() to add $inlinecount=allpages query option to the query which will include the count in the response. Then to not download all the entities from server, you can use Take(0) which will add $top=0 and thus return empty result set. But the inline count will still contain the right number.
You can access the inline count on the QueryOperationResponse.TotalCount property.
Something like this:
NetflixCatalog ctx = new NetflixCatalog(new Uri("http://netflix.cloudapp.net/Catalog"));
var q = (DataServiceQuery<Genre>)ctx.Genres.IncludeTotalCount().Take(0);
q.BeginExecute((ar) =>
{
QueryOperationResponse<Genre> r = (QueryOperationResponse<Genre>)q.EndExecute(ar);
r.TotalCount.ToString(); // Use the count in whatever way you need
}, null);
It works in LinqPad 4 using C# 4.0
var count = (from g in Genres select g).Count();
count.Dump();
Result: 518
In LinqPad 2 using C# 3.0 the error appears.

Resources