Elasticsearch.Net and NEST, IGetResponse to document POCO? - c#

I'm retrieving a document from Elasticsearch using the client.Get<MyDocument>(getRequest) syntax however the IGetResponse I retrieve is basically useless. It contains no fields of the document I want and basically only tells me that the .Get was successful (and includes the Id of the document I'm trying to get)
Here is my code:
TypeName typeName = TypeName.From<MyDocument>();
GetRequest request = new GetRequest(Index, typeName, new Id("R" + id));
// I can't get any of the fields I want from this object:
IGetResponse<MyDocument> result = Client.Get<MyDocument>(request);
My question is do I need to cast the IGetResponse<MyDocument> to a MyDocument somehow? Is there some step I'm missing here?
EDIT: P.S.: result.Found is true so it definitely succeeds in getting the document

Figured it out: the property on the IGetResponse<MyDocument> I want is Source. Its the actual document object.
e.g.:
IGetResponse<MyDocument> result = Client.Get<MyDocument>(request);
if (result.Found)
{
MyDocument myDocument = result.Source;
}

From the documentation:
The Get() call returns an IGetResponse that holds the requested document as well as other meta data returned from Elasticsearch.
response.Source holds the document.

Related

Cannot access or find reference to System.Xml.Linq.LineInfoAnnotation. Why is this?

I have an application which takes an XML document and sorts it by certain attributes. I have information associated with each line of the XML document which I want to include in the sorted document. In order to do this,
When I load the file, I make sure the line info is loaded using XDocument.Load(file, LoadOptions.SetLineInfo).
Then I recursively iterate over each XElement and get its line info. When I ran the app, I noticed that each XElement has two annotations,
one of type System.Xml.Linq.LineInfoAnnotation
and one of type System.Xml.Linq.LineInfoEndElementAnnotation.
They contain the info that I need but in private fields.
I can't find any information on these classes, I can't instantiate them, they do not appear in the Object browser under System.Xml.Linq. Yet they exist and I can run "GetType()" on them and get information about the class.
If they exist, why are they not in MSDN references and why can't I instantiate them or extend them? Why can't I find them in the object browser?
P.S. My workaround for this was to use reflection to get the information contained inside each element. But I still can't pass a class name to tell the method what type it is, I have to isolate the object from XElement.Annotations(typeof(object)), and then run GetType() on it. I've illustrated this below.
public object GetInstanceField(Type type, object instance, string fieldName)
{
//reflective method that gets value of private field
}
XElement xEl = existingXElement; //existingXElement is passed in
var annotations = xEl.Annotations(typeof(object)); //contains two objects, start and end LineInfoAnnotation
var start = annotations.First();
var end = annotations.Last();
var startLineNumber = GetInstanceField(start.GetType(), start, lineNumber); //lineNumber is private field I'm trying to access.
var endLineNumber = GetInstanceField(end.GetType(), end, lineNumber);
This code works, but again, I can't just tell the method "typeof(LineInfoAnnotation)", instead I have to do GetType on the existing object. I cannot make sense of this.
Those classes are private - an implementation detail, if you will.
All XObjects (elements, attributes) implement the IXmlLineInfo interface - but they implement the inteface explicitly, so you must perform a cast to access the properties.
Once you have your IXmlLineInfo, you can use the properties LineNumber and LinePosition.
var data =
#"<example>
<someElement
someAttribute=""val"">
</someElement></example>
";
var doc = XDocument.Load(new MemoryStream(Encoding.UTF8.GetBytes(data)), LoadOptions.SetLineInfo);
foreach(var element in doc.Descendants()) {
var elLineInfo = element as IXmlLineInfo;
Console.Out.WriteLine(
$"Element '{element.Name}' at {elLineInfo.LineNumber}:{elLineInfo.LinePosition}");
foreach(var attr in element.Attributes()) {
var attrLineInfo = attr as IXmlLineInfo;
Console.Out.WriteLine(
$"Attribute '{attr.Name}' at {attrLineInfo.LineNumber}:{attrLineInfo.LinePosition}");
}
}
Output:
Element 'example' at 1:2
Element 'someElement' at 2:2
Attribute 'someAttribute' at 3:3
To get the EndElement information, you have to use a plain old XML reader, since the XObject api doesn't expose any information about where the element ends.
using(var reader = doc.CreateReader()) {
while(reader.Read()) {
var lineInfo = reader as IXmlLineInfo;
Console.Out.WriteLine($"{reader.NodeType} {reader.Name} at {lineInfo.LineNumber}:{lineInfo.LinePosition}");
if(reader.NodeType == XmlNodeType.Element && reader.HasAttributes) {
while(reader.MoveToNextAttribute()) {
Console.Out.WriteLine($"{reader.NodeType} {reader.Name} at {lineInfo.LineNumber}:{lineInfo.LinePosition}");
}
}
}
}
Output:
Element example at 1:2
Element someElement at 2:2
Attribute someAttribute at 3:3
EndElement someElement at 5:5
EndElement example at 5:19

How do I retrieve a specific neo4j node property using neo4jclient?

I have some data stored as a neo4j node. This node has some property that is not described by the associated C# class, and thus is not automatically mapped back to the class when the neo4jclient query returns.
As an example, this C# class:
public class Node {
public string name;
public int number;
public CustomClass data;
}
stored in neo4j, then retrieved with the following neo4jclient fluent code:
var query = client.Cypher
.Match("(n:Node)")
.Return(n => n.As<Node>())
.Results;
will populate a Node object with name and number, but leave a null reference to the CustomClass object.
To solve this problem I have serialized the CustomClass as a JSON string, and stored it in neo4j as a string property. In order to deserialize this JSON class, I need to retrieve the JSON string property from the Node stored in neo4j.
The neo4jclient documentation recommends the following:
.Return(() => new {
JSONString = Return.As<string>("matchedNode.JSONProperties")
})
however this is invalid code. The Return after JSONString = does not exist in that context.
See Answer.
How can I get the JSONPropeties string out of the database?
The given code works exactly as expected, you just need to include the correct neo4jclient reference. In this case it is
using Neo4jClient.Cypher;
with that, Return is no longer undefined. This is also where the All class is, if you need access to all matched elements.
Further to your answer, aside from adding the
using Neo4jClient.Cypher
You could also choose just to return the Node properties like so:
var query = client.Cypher
.Match("(n:Node)")
.Return(n => n.As<Node>().name) //<-- returning just the property
.Results;

How to Get a typed DTO with only some fields from Elasticsearch with Nest?

I'm trying to Get a result from Elasticsearch 1.7.0 using NEST 1.7.1. My documents contain many fields, but I'm interested in only one of them. I would prefer to get a typed result representing a partial document.
I'm using something along the lines of this code:
var settings = new ConnectionSettings(new Uri(url)).SetDefaultIndex("MyIndex");
var client = new ElasticClient(settings);
var result = client.Get<MyDtoPartial>(g => g
.Type("myDocType")
.Id("abcdefg123456")
.Fields("doc.subids")
);
Where MyDtoPartial currently looks like this:
public class MyDtoPartial
{
[JsonProperty("doc.subids")]
public IList<string> SubIds { get; set; }
// Other properties of my documents are not mapped, in this
// context I only want the SubIds.
}
In the debugger I can drill into result.Fields and see that the first in that dictionary has a value rendered by the debugger along these lines:
{[doc.subids, [ "12354adsf-123fasd", "2134fa34a-213123" ...
I can also see the Elasticsearch request that was made, which was like this:
http://myserver:12345/MyIndex/myDocType/abcdefg123456?fields=doc.subids
And it returns this type of json:
{
"_index": "MyIndex",
"_type": "myDocType",
"_id": "abcdefg123456",
"_version": 1,
"found": true,
"fields": {
"doc.subids": ["12354adsf-123fasd",
"2134fa34a-213123",
"adfasdfew324-asd"]
}
}
So I have a feelling my request is okay, because that is the kind of response I'd expect.
However, my goal was to get an instance of MyDtoPartial with a fully populated SubIds property. However, the result doesn't seem to contain any kind of property of type MyDtoPartial.
I've gone through the Nest Get docs, which actually led to the above code.
What is the proper way to Get a proper typed single document with only some fields from Elastic with Nest?
If you mention .Fields(...), Source will always be null. If you remove .Fields(...), then Source should be of type MyDtoPartial and give you the desired results. The reason you still get Source as null may be because in the mapping of myDocType, _source field is disabled. Check the definition of myDocType by executing GET <index name>/_mapping/myDocType. If _source is disabled, there is no way Nest will give you a concrete object of MyDtoPartial in its response for this type.
If you have _source enabled but only want to fetch a subset of fields, then you can use source filtering instead of fields to specify which fields you want and which ones you do not want to be returned in the response.
var result = client.Get<MyDtoPartial>(g => g
.Type("myDocType")
.Id("abcdefg123456")
.SourceInclude("doc.subids")
);
Now result.Source will be an object of MyDtoPartial where all fields except SubIds will be null and SubIds will have the expected value.

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

XML LINQ query returns nothing

I'm trying to parse an xml file using LINQ, but as I understand the query returns null. (It's WP7)
Here's the code:
var resultQuery = from q in XElement.Parse(result).Elements("Question")
select new Question
{
QuestionId = q.Attribute("id").Value,
Type = q.Element("Question").Attribute("type").Value,
Subject = q.Element("Subject").Value,
Content = q.Element("Content").Value,
Date = q.Element("Date").Value,
Timestamp = q.Element("Timestamp").Value,
Link = q.Element("Link").Value,
CategoryId = q.Element("Category").Attribute("id").Value,
UserId = q.Element("UserId").Value,
UserNick = q.Element("UserNick").Value,
UserPhotoURL = q.Element("UserPhotoURL").Value,
NumAnswers = q.Element("NumAnswers").Value,
NumComments = q.Element("NumComments").Value,
};
"result" is the xml string, just like this one.
http://i48.tinypic.com/1ex5s.jpg (couldn't post properly formatted text so here's a pic : P )
Error:
http://i48.tinypic.com/2uyk2ok.jpg
Sorry, if I haven't explained it properly and if this has already been asked (tried searching but didn't help).
You have run into an XML namespace problem. When you are just querying "Question", the string is translated into an XName with the default namespace. There are no elements in the default namespace in your XML, only elements in the urn:yahoo:answers namespace (see the top level element, where it says xmlns="urn:yahoo:answers").
You need to query the correct XML namespace, like this:
var ns = new XNameSpace("urn:yahoo:answers");
var resultQuery = from q in XElement.Parse(result).Elements(ns + "Question");
When picking out the individual properties, remember to add the namespace also.
XName is a class that represents an XML name, which might have a namespace defined by XNameSpace. These two classes has an implicit conversion operator implemented that allows you to implicitly convert from string to XName. This is the reason the calls work by just specifying a string name, but only when the elements are in the default namespace.
The implicitness of this makes it very easy easier to work with XML namespaces, but when one does not know the mechanism behind, it gets confusing very quickly. The XNameclass documentation has some excellent examples.
Two ways to fix it:
Add the root element was part since Elements only search one level - XElement.Parse(result).Root.Elements("Question")
Use the Descendants method since that will search the entire xml tree.

Categories