ElasticSearch NEST searchresponse.hits data not populated - c#

I have an index in ElasticSearch. I have setup an ASP.NET core lambda serverless app. I inject IElasticClient into my controller.
I want my controller to be signed as public IEnumerable<StreamSummary> Get() and the ElasticSearch query I would have thought to be as as simple as return searchResponse.Hits.Select(h => h.Source).ToList(); but this is not the case as can be seen below.
I query my Index with something basic like this
var searchResponse = elasticClient.Search<StreamSummary>(s => s
.Index("myindex")
.Query(q => q.MatchAll()
)
);
When I log how many "hits" I get, it is the correct amount (searchResponse.Hits.Count).
When I set my controller return IEnumerable<StreamSummary> and use searchResponse.Hits.Select(h => h.Source).ToList() the list is the correct number but every entry has no values. DateTime fields are all Datetime.Min strings are empty, nullables are null. It's basically an empty/freshly instantiated class and not populated/serialised as I would have expected it to be.
When I iterate the hits and display/log Index,Type and Id, this all works and displays exactly as I expect. The Issue I am having is with _source (Source Property NEST).
foreach (var searchResponseHit in searchResponse.Hits)
{
hits.Add(searchResponseHit.Index);
hits.Add(searchResponseHit.Type);
hits.Add(searchResponseHit.Id); searchResponseHit.Source
searchResponseHit.Source // here is the problem, no populated StreamSummary data}
Where hits is List<string> hits = new List<string>();
So I dig a bit further and in my Startup.cs I DisableDirectStreaming() on ConnectionSettings() so I can get at the request/response buffered byte streams.
I then do the following:
var requestJson = System.Text.Encoding.UTF8.GetString(searchResponse.ApiCall.RequestBodyInBytes);
var responseJson = System.Text.Encoding.UTF8.GetString(searchResponse.ApiCall.ResponseBodyInBytes);
return searchResponse.ApiCall.HttpMethod.ToString() + searchResponse.ApiCall.Uri + "\n" + requestJson + "\r\n" + responseJson;
This is an inccomplete/part response but as you can see it contains a lot of escape characters etc. (Could this be part of the problem, a serialisation issue??)
\"hits\" : {\n \"total\" : {\n \"value\" : 9,\n \"relation\" : \"eq\"\n },\n \"max_score\" : 1.0,\n \"hits\" : [\n {\n \"_index\" : \"accounts\",\n \"_type\" : \"_doc\",\n \"_id\" : \"U3P 1 8fb4b770-4e29-4f0d-aec8-1bcd4199005f\",\n \"_score\" : 1.0,\n \"_source\" : {\n \"ProcessingResponse\" : \"Success\",\n \"ProviderId\" : 6,\n
Connection/Client:
var uri = new Uri(Configuration["ElasticSearch:Url"]);
var httpConnection = new AwsHttpConnection(new BasicAWSCredentials(Configuration["AWS:AwsAccessKeyId"], Configuration["AWS:AwsSecretAccessKey"]),
RegionEndpoint.GetBySystemName(Configuration["ElasticSearch:Region"]));
var pool = new SingleNodeConnectionPool(uri);
var config = new ConnectionSettings(pool, httpConnection).DefaultIndex(Startup.ElasticSearchIndex)
.DisableDirectStreaming().PrettyJson();
var elasticClient = new ElasticClient(config);
services.AddSingleton<IElasticClient>(elasticClient);
POCO is:
public class StreamSummary : ResponseSummary
{
public string Type { get; set; }
public int? ProviderId { get; set; }
public string ProviderAccount { get; set; }
public int? ProcessingTime { get; set; }
public DateTime ProcessedTimeUtc { get; set; }
}
where Response Summary Contains a couple of nullable decimals(but also has an IList<AnotherClass>)
Help Appreciated.

If I'm not mistaken, this is what you're after:
searchResponse.Documents

Related

MongoDB run command with json return

My overall goal is to run a query that shows all the collections with the amount of data / counts and so on.
I found the following command/query and try to run it like this:
MongoClient client = new MongoClient(server);
var db = client.GetDatabase(database);
const string mongoQueryA = "var collectionNames = db.getCollectionNames(), stats = []; " +
"collectionNames.forEach(function(n) { stats.push(db[n].stats()); }); " +
"stats = stats.sort(function(a, b) { return b['size'] - a['size']; }); ";
var command = new JsonCommand<BsonDocument>(mongoQueryA);
var test = db.RunCommand<BsonDocument>(command);
And the code fails here. Exceptions:
JSON reader was expecting a value but found 'var'.
My understanding is that this shoul be run as command?
Running the query in Robot T works as expected. Bonus the plan was to return the data in the following format ( Based on Json from running query manualy in Robot T)
class MongoCollectionInfo
{
public string ns { get; set; }
public long size { get; set; }
public long count { get; set; }
public long avgObjSize { get; set; }
public long storageSize { get; set; }
public long nindexes { get; set; }
public long totalIndexSize { get; set; }
}
What you're trying to achieve can be done purely in C# as it looks like what you've attempted to do is send javascript to the server to be executed which is not how commands work.
To start off with we'll need to get a list of all the collections within a database inside the MongoDB instance.
var client = new MongoClient();
var db = client.GetDatabase("test");
var collectionNames = await (await db.ListCollectionNamesAsync()).ToListAsync();
once we've got the collection names (collectionNames) we can then go ask the database for the stats on that collection by issue a command.
var allCollStats = new List<BsonDocument>(collectionNames.Count);
foreach (var name in collectionNames)
{
var collStatsResult = await db.RunCommandAsync(new BsonDocumentCommand<BsonDocument>(new BsonDocument("collStats", name)));
allCollStats.Add(collStatsResult);
}
The allCollStats then will hold all the stats for the collections and we can then use it as we see fit
foreach (var collStats in allCollStats)
{
Console.WriteLine($"ns: {collStats["ns"]}, size: {collStats["size"]}");
}
// ns: test.test, size: 117
// ns: test.people, size: 5092
Also, if you wanted to use a typed result instead of a BsonDocument you can pass this in as the generic agrument to the command.
var collStatsResult = await db.RunCommandAsync(new BsonDocumentCommand<MongoCollectionInfo>(new BsonDocument("collStats", name)));
Console.WriteLine($"ns: {collStatsResult .ns}, size: {collStatsResult .size}");

How to retrieve a property from an object within a dictionary in a MongoDB document?

Our Mongo data looks like this:
{
"_id" : ObjectId("542d881b8bc641bbee1f8509"),
"ExtendedProperties" : {
"Context" : {
"_t" : "LoggingContext",
"DeclaringTypeName" : "EndpointConfig"
}
}
}
In the C# code, the ExtendedProperties are represented as follows:
public class LogEntry
{
public IDictionary<string, object> ExtendedProperties { get; set; }
}
I have tried every method I can find to be able to query against the value of DeclaringTypeName. Nothing seems to work, as shown in the following code:
// This throws an UnsupportedOperationException with the following message:
// Unable to determine the serialization information for the expression: (LogEntry e) => e.ExtendedProperties.get_Item("DeclaringTypeName").ToString().
query.Add(Query<LogEntry>.EQ(e => ((LoggingContext)e.ExtendedProperties["Context"]), this.DeclaringTypeName ));
// This returns zero matching rows:
query.Add(Query.EQ("ExtendedProperties.Context.DeclaringTypeName", this.DeclaringTypeName));
// This returns zero matching rows:
query.Add(Query.ElemMatch("ExtendedProperties.Context", Query.EQ("DeclaringTypeName", this.DeclaringTypeName)));
// This reports that ExtendedProperties must implement a specific interface and must not return null:
query.Add(Query<LogEntry>.ElemMatch(e => e.ExtendedProperties, qb => Query.EQ("Context.DeclaringTypeName", this.DeclaringTypeName)));
For clarity, I have researched every StackOverflow, CodePlex, and Mongo.org thread I can find, and have as yet been unable to resolve this correctly.
Naturally, it's going to be something I'm doing wrong.
Someone please throw me a bone.
I defined the LogEntry class as
public class LogEntry
{
public ObjectId Id { get; set; }
public IDictionary<string, object> ExtendedProperties { get; set; }
}
then I inserted the sample document by
var log = new LogEntry
{
ExtendedProperties = new Dictionary<string, object>
{
{
"Context", new LoggingContext
{
DeclaringTypeName = "EndpointConfig"
}
}
}
};
collection.Insert(log);
then I performed the query by:
var rawQuery = Query.EQ("ExtendedProperties.Context.DeclaringTypeName", "EndpointConfig");
var query = new List<IMongoQuery>();
query.Add(rawQuery);
var rawResult = collection.Find(rawQuery).ToList();
the query will send mongo below query
db.messages.find({ "ExtendedProperties.Context.DeclaringTypeName" : "EndpointConfig" })
And I got the result

Deserialize Json with no name Fields and Format string, array

I have the following json object:
[
"sd",
[
"sdg\u0026e",
"sdlc",
"sdccu",
"sdsu webportal",
"sdsu",
"sdsu blackboard",
"sdcc",
"sd card",
"sdn",
"sdro"
]
]
Obtained from google suggest with this URL:
http://suggestqueries.google.com/complete/search?output=firefox&hl=en&q=sd
I have tried deserializing it like this:
dynamic objson = JsonConvert.DeserializeObject(res);
But it is not useful because I need it into a class object.
And also using types:
public class SuggestClass
{
public string search { get; set; }
public string[] terms { get; set; }
}
var result = JsonConvert.DeserializeObject<SuggestClass>(res);
But it always throw exception.
I do not know how can I do it without having name fields.
EDIT:
Another JSON:
["text",["textura","textos bonitos","texto argumentativo","textos","textos de amor","texto expositivo","texturas minecraft","textos de reflexion","texture pack minecraft","textos en ingles"]]
That's tricky...
But since it's an array, you could create a factory method to parse SuggestClass out of given JArray.
public void SomeMethod()
{
string json =
"[\"sd\",[\"sdg\u0026e\",\"sdlc\",\"sdccu\"" +
",\"sdsu webportal\",\"sdsu\",\"sdsu blackboard\","+
"\"sdcc\",\"sd card\",\"sdn\",\"sdro\"]]";
var factory = new Factory();
var suggest = factory.Create(json);
Console.WriteLine(suggest);
}
public class Factory
{
public SuggestClass Create(string json)
{
var array = JArray.Parse(json);
string search = array[0].ToString();
string[] terms = array[1].ToArray().Select(item => item.ToString()).ToArray();
return new SuggestClass {Search = search, Terms = terms};
}
}
public class SuggestClass
{
public string Search { get; set; }
public IEnumerable<string> Terms { get; set; }
public override string ToString()
{
return string.Format("Search={0},Terms=[{1}]",
Search, string.Join(",", Terms));
}
}
Would print to console:
Search=sd,Terms=[sdg&e,sdlc,sdccu,sdsu webportal,sdsu,sdsu blackboard,sdcc,sd card,sdn,sdro]
And the other JSON you provided:
Search=sd,Terms=[sdg&e,sdlc,sdccu,sdsu webportal,sdsu,sdsu blackboard,sdcc,sd card,sdn,sdro]
Search=text,Terms=[textura,textos bonitos,texto argumentativo,textos,textos de amor,texto expositivo,texturas minecraft,textos de reflexion,texture pack minecraft,textos en ingles]
Just used the JSON visualizer in visual studio. This is how it looks like.
It is an array of multiple types. The following code can be used to parse it. But it is not perfect yet.
var objson = JsonConvert.DeserializeObject<object[]>(res);
So I think #Mikko answer has a better approach..

RestSharp (C#) - Deserialize JSON Objects to NULL

I'm trying to understand REST by utilizing RestSharp in my application.
Here is a bit of JSON that my client is returning:
{
"result" : {
"object_type" : "session",
"user_id" : "FEE3CBD4-5D35-11E3-A42A-606A40E381E5",
"object_name" : "Session",
"id" : "2F2968B6-5D37-11E3-89F4-5D6A40E381E5"
}
}
Here is my class object:
public class TGCResult : IDeserializer
{
public string object_type {get; set;}
public string user_id { get; set; }
public string object_name { get; set; }
public string id { get; set; }
public TGCResult()
{
}
public override string ToString()
{
return "object_type = " + object_type + "\nuser_id = " + user_id + "\nobject_name = " + object_name + "\nid = " + id;
}
}
And here is the code in which I am retrieving the JSON and attempting to deserialize:
var client = new RestClient("https://www.xxxxxxxxx.com");
var request = new RestRequest("/api/session", Method.POST);
request.AddParameter("username", "JSventoraGD");
request.AddParameter("password", "xxxxxxxxxxxxxxxx");
request.AddParameter("api_key_id", "xxxxxxxxxxxxxxxxxxx");
request.RequestFormat = DataFormat.Json;
var asyncHandle = client.ExecuteAsync<TGCResult>(request, response =>
{
TxtTest.Text = response.Data.ToString();
});
asyncHandle.Abort();
When doing this, I can see the data is returned to my application correctly, but the Data in my response always has NULL values. Any ideas on why this might be happening? Am I supposed to manually deserialize the JSON given the content? The samples I've seen online have a very similar setup to mine, I'm lost... any help is greatly appreciated!
var asyncHandle=client.ExecuteAsync(request,response=>{
String res=response.Content;
T obj = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(res);
});
Try adding
request.RootElement = "result";
I have just spent a couple of hours trying to get a simple GET request to parse anything but null, setting RootElement helped me. Bit surprised i needed it since you set your type class several other places in the code.
Try using it like this
var asyncHandle = client.ExecuteAsync(request, response =>
{
var json = response.Content;
// deserialize to TGCResult
});
This works fine for me
It seems you need to have another class as your Data type.
public class TGCResultContainer
{
public TGCResult result { get; set; }
}
Also, your TGCResult class does not need to implement IDeserializer.

A generic async method for requesting optionally paginated lists of different resources

I am accessing a REST API which returns a list of resources in JSON format:
{
"products": [
{ ... },
{ ... }
]
}
When the list is big (>50 items) the response becomes paginated and an additional pagination item is added to the root node of returned JSON like that:
{
"pagination": {
"results" : 490,
"page" : 1,
"page_size" : 50,
"pages" : 10
},
"products": [
{ ... },
{ ... }
]
}
In order to cater for that I have a PaginatedList class (probably not the best name) which looks like that:
public class PaginatedList
{
[JsonProperty("pagination")]
public Pagination Pagination { get; set; }
}
a ProductList class that looks like that:
public class ProductList : PaginatedList
{
[JsonProperty("products")]
public List<Product> Products { get; set; }
}
a Pagination class like that:
public class Pagination
{
[JsonProperty("results")]
public int Results { get; set; }
[JsonProperty("page")]
public int Page { get; set; }
[JsonProperty("page_size")]
public int PageSize { get; set; }
[JsonProperty("pages")]
public int Pages { get; set; }
}
To retrieve my resource I use:
public List<Product> GetProducts()
{
return getResourceAsync<ProductList>(productsResourceName).Result.Products;
}
and:
async Task<T> getResourceListAsync<T>(string resourceName)
{
var url = string.Concat(BaseUrl, resourceName);
var credentials = new NetworkCredential(Username, Password);
var handler = new HttpClientHandler { Credentials = credentials };
using (var client = new HttpClient(handler)) {
var response = await client.GetAsync(url);
var contentString = await response.Content.ReadAsStringAsync();
var resource = await JsonConvert.DeserializeObjectAsync<T>(contentString);
return resource;
}
}
Adding support for pagination inside the GetProducts method would be pretty easy but that would mean duplicating very similar code for every type of resource (products, customers, suppliers, etc). The question is, how do I get the getResourceListAsync method so that it supports paginated and non-paginated lists AND works for different resources?
To make this scenario possible :
// supports both scenarios
var json = #"{ 'products': [ { Id : 1 , Name : 'A' }, { Id : 2 , Name : 'B' } ] }";
var results = Helper.ParseFromJsonResult<Product>(json);
var anotherJson = #"{ 'pagination': {
'results' : 490,
'page' : 1,
'page_size' : 50,
'pages' : 10
},
'products': [
{ Id : 1 , Name : 'A' }, { Id : 2 , Name : 'B' }
]}";
var anotherResults = Helper.ParseFromJsonResult<Product>(anotherJson);
You can use this codes :
public static class Helper
{
private static readonly PluralizationService _NameService =
PluralizationService.CreateService(new CultureInfo("en-us"));
// Provides Plural names [ products for Product ]
// to determinate the name of the result for example products for Product class
private static ModuleBuilder _ModuleBuilder;
static Helper()
{
var asmName = new AssemblyName();
asmName.Name = "MyHelpers";
AssemblyBuilder asmBuilder = Thread.GetDomain().DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
_ModuleBuilder = asmBuilder.DefineDynamicModule("MyHelpers");
// Assembly to put runtime generated classes to that.
}
private static readonly IDictionary<Type, Type> _HelpersCache = new Dictionary<Type, Type>();
public static List<T> ParseFromJsonResult<T>(String json)
{
Type resultType = null;
var entityType = typeof(T);
var pluralName = _NameService.Pluralize(entityType.Name).ToLowerInvariant();
// products for Product class
if (_HelpersCache.ContainsKey(entityType))
{
// better performance
resultType = _HelpersCache[entityType];
}
else
{
// need another runtime generated class
// result :
/* public class products
{
public List<Product> products;
}
*/
TypeBuilder resultTypeBuilder = _ModuleBuilder.DefineType(pluralName, TypeAttributes.Public);
FieldBuilder field = resultTypeBuilder.DefineField(pluralName, typeof(List<T>), FieldAttributes.Public);
resultType = resultTypeBuilder.CreateType();
_HelpersCache.Add(entityType, resultType);
}
Object result = JsonConvert.DeserializeObject(json, resultType);
return (List<T>)resultType.GetField(pluralName).GetValue(result); // get products field value
}
}
I hope this helps, Let me know if you want more information
Good luck
I see your problem. Was stuck in the same one actually. What I ended up doing was an interface that my classes i want to paginte implement. As I did this for export of data it is called IExportable. This thing returns a PagedResult and takes a IPagedRequest.
So the point is that my code in the task is just working with interface and does not actually know what the end type is. PagedResult is a type with generics.
Hope it helps, trying to write this from top of my head without the source.

Categories