Handling IGetResponse on Nest - elasticsearch

I am using the Get API of Nest, but I don't know how to typecast the respone (IGetResponse) to the specific type of the document, something like this:
var response = client.Get<MyDocument>(documentId);
return response.Document(); // Or something like this that returns a MyDocument type
Also, is there a way to get the document for another unique field or only the Id is accepted?

response.Source holds document of type MyDocument.
As documentation says, you can use get api to get documents only by their ids.
You can tell elasticsearch to treat other field from document as Id.
With NEST you can do this as follows:
var indicesOperationResponse = client.CreateIndex(descriptor => descriptor
.Index(indexName)
.AddMapping<Document>(m => m.IdField(f => f.Path("uniqueValue"))));
client.Index(new Document{UniqueValue = "value1"});
var getResponse = client.Get<Document>(g => g.Id("value1"));
My document class:
public class Document
{
public string UniqueValue { get; set; }
}

Related

Insert a Dynamic into Elastic Document through nest

var doc = new ESDoc {
Field1 = "test1",
Field2 = 3,
ExtraData = 'dynamic object',
Index = "myindex"
};
ElasticClient.Index(doc, s => s.Index(doc.Index));
This is in essence what I am trying to do. I have a document object, and I am wanting to add to it a dynamic object that allows us to through whatever customer specific data we want in there. I have no need to ever search or do any querying on it, I just need to hold it for CYA issues.
This results in ExtraData having a value_kind = 1.
I tried to JsonSerializer.Serialize the data and it came out in a triple escaped string.
I have seen people trying to create a entire document of dynamic data, and using a object cast, but I feel that isnt the answer here because I have a document that I want to add a dynamic object too.
NEST and Elaticsearch.Net 7.16.0
You can do this with the following:
You need to have a type to model the dynamic behaviour of the field. Now, you could specify it as dynamic on the ESDoc class, but you'll need an instance of a type to assign to it and Nest deserializes JSON to a Dictionary<string, object> for a dynamic member. Because of this, you might want to use the DynamicDictionary type in Elasticsearch.Net (a dependency of Nest) that is a dictionary with dynamic access behaviour
public class ESDoc
{
public string Field1 { get; set; }
public int Field2 { get; set; }
public DynamicDictionary ExtraData { get; set; }
}
Map ExtraData as object data type with enabled: false, so that it is not parsed and indexed
var createIndexResponse = client.Indices.Create("myindex", c => c
.Map<ESDoc>(m => m
.Properties(p => p
.Text(t => t
.Name(f => f.Field1)
)
.Number(t => t
.Name(f => f.Field2)
.Type(NumberType.Integer)
)
.Object<object>(o => o
.Name(f => f.ExtraData)
.Enabled(false)
)
)
)
);
Now index a doc, refresh the index, and search for it to ensure it deserializes as expected
dynamic extraData = new DynamicDictionary();
extraData.foo = "Foo";
extraData.bar = new DynamicDictionary
{
["baz"] = new DynamicValue("Baz")
};
var doc = new ESDoc
{
Field1 = "test1",
Field2 = 3,
ExtraData = extraData
};
client.Index(doc, s => s.Index("myindex"));
client.Indices.Refresh("myindex");
var searchResponse = client.Search<ESDoc>(s => s.Index("myindex"));
var firstDoc = searchResponse.Documents.First();
var baz = firstDoc.ExtraData["bar"]["baz"];
Console.WriteLine($"{baz}");

Lite DB not finding inner object query

I have two objects.
[DataContract]
public class Record
{
[DataMember]
public string Id { get; set; }
}
And this class:
public class BatteryStatus : Record
{
[DataMember]
public DateTime RetrieveTime { get; set; }
}
I'm using Lite DB as a local NoSQL option to query and save the data. I'm needing to find and delete the values based after some time. Here's my code doing so:
var col = db.GetCollection<BatteryStatus>(CollectionName);
var test = col.FindAll()
.Where(x => x.Id == status.Id).ToList();
var result = col.Find(Query.EQ("Id", status.Id.ToString())).ToList();
Test returns with the with the object, but the result value doesn't. Lite DB only uses the Query or the BSONId as a way to delete an object. I don't have a BSON id attached to it (it's a referenced definition so I can't change it).
How can I use the "Query" function in order to get a nested value so I can delete it?
Classes has properties, BSON documents has fields. By default, LiteDB convert all property names to same name in BSON document except _id field which is document identifier.
If you want query using Linq, you will use properties expressions. If you are using Query object class, you must use field name.
var result = col.FindById(123);
// or
var result = col.FindOne(x => x.Id == 123);
// or
var result = col.FindOne(Query.EQ("_id", 123));
Find using _id always returns 1 (or zero) document.
I figured out the problem with LiteDB, since I was using the property name of "Id", the BSON interpreted that as the "_id" of the JSON object, and merging their two values. I solve the issue by renaming the "Id" property to something else.

Attribute based index hints change my results

I have this query that hasn't changed since I first got it working:
ISearchResponse<Series> response = await IndexManager.GetClient()
.SearchAsync<Series>(r => r
.Filter(f => f.Term<Role>(t => t.ReleasableTo.First(), Role.Visitor))
.SortDescending(ser => ser.EndDate)
.Size(1));
My IndexManager.GetClient() is simply responsible for setting up my connection to ElasticSearch, and ensuring that the indexes are built properly. The rest of the code gets the most recent article series that is releasable to the general public.
Inside the IndexManager I set up explicit index mapping, and when I did that I got results from my query every time. The code looked like this:
client.Map<Series>(m => m.Dynamic(DynamicMappingOption.Allow)
.DynamicTemplates(t => t
.Add(a => a.Name("releasableTo").Match("*releasableTo").MatchMappingType("string").Mapping(map => map.String(s => s.Index(FieldIndexOption.NotAnalyzed))))
.Add(a => a.Name("id").Match("*id").MatchMappingType("string").Mapping(map => map.String(s => s.Index(FieldIndexOption.NotAnalyzed))))
.Add(a => a.Name("services").Match("*amPm").MatchMappingType("string").Mapping(map => map.String(s => s.Index(FieldIndexOption.NotAnalyzed)))
.Match("*dayOfWeek").MatchMappingType("string").Mapping(map => map.String(s => s.Index(FieldIndexOption.NotAnalyzed))))
.Add(a => a.Name("urls").Match("*Url").MatchMappingType("string").Mapping(map => map.String(s => s.Index(FieldIndexOption.NotAnalyzed))))
));
While all well and good, doing this for every type we stored wasn't really going to scale well. So I made a conscious decision to use the attributes and map it that way:
// In IndexManager
client.Map<T>(m => m.MapFromAttributes());
// In the type definition
class Series
{
// ....
[DataMember]
[ElasticProperty(Index = FieldIndexOption.NotAnalyzed, Store = true)]
public HashSet<Role> ReleasableTo { get; set; }
// ....
}
As soon as I do this, I no longer get results. When I look at my indexes in Kibana, I see my 'releasableTo' field is not analyzed and it is indexed. However the query I wrote no longer works. If I remove the filter clause I get results, but I really need that to work.
What am I missing? How do I get my query to work again?
It appears that the ElasticSearch attributes to provide indexing hints don't know what to do with enums.
The problem turned out to be the fact that the Role type was an enumeration. The client.Map<Series>(m => m.MapFromAttributes()) call skipped that property. At run time, it dynamically maps the property to a string.
// In the type definition
class Series
{
// ....
[DataMember]
[ElasticProperty(Index = FieldIndexOption.NotAnalyzed, Store = true)]
public HashSet<Role> ReleasableTo { get; set; }
// ....
}
To get the field properly indexed I had to explicitly set it's type in the ElasticProperty attribute. Changing the code to this:
// In the type definition
class Series
{
// ....
[DataMember]
[ElasticProperty(Index = FieldIndexOption.NotAnalyzed, Type = FieldType.String, Store = true)]
public HashSet<Role> ReleasableTo { get; set; }
// ....
}
made my query work again. The moral of the story is that unless it's a primitive type, be explicit when setting the field type.

Elastic Search NEST - Search returns no records when we mention Type name

Tried Code:-
public class Company
{
public long Number { get; set; }
public string Name{ get; set; }
}
My problem is : If I mention Type name within Search Tag I couldn't get any result.
1) Shows Result
var rowsList= client.Search(s => s.MatchAll()).Documents.ToList();
2) Returns 0 rows
var newr = client.Search<Company>(s => s.MatchAll()).Documents.ToList();
The typed search defaults to
/[inferred_index]/[inferred_typename]/_search
If you want to search through all indices and types you will have to explicit about it
client.Search<Company>(s=>s
.AllIndices()
.AllTypes()
.MatchAll()
)
this will do the search on /_search
You can control how nest infers indices and type names like so:
var settings = new ConnectionSettings(uri)
.SetDefaultIndex("my-default-index")
.MapDefaultTypeNames(d=>d
.Add(typeof(Company), "company-type")
)
MapDefaultTypeIndices(d=>d
.Add(typeof(Company), "company-index")
)
SetDefaultTypeNameInferrer(s=>s.ToLowerInvariant())
Now if you search for <Company> it will default to /company-index/company-type/_search
if you do a search for <Person> it will query on /my-default-index/person/_search.

NEST mapping of Dictionary<string,object>

Im trying to use NEST and canĀ“t figure out how to use it together with this class
public class Metric {
public DateTime Timestamp { get; set; }
public Dictionary<string,object> Measurement { get; set; }
}
How do i use the new fluent mapping with a class like this?
Im planning to use i like this:
var mesurements = new Dictionary<string, object>();
mesurements["visits"] = 1;
mesurements["url"] = new string[] {"/help", "/about"};
connection.Index(new Metric() {
Timestamp = DateTime.UtcNow,
Measurement = mesurements
});
Will it be possible to write a query against the dictionary? If I wanted to get all Metrics from yesterday with a mesurenemt with a key name "visits", how will that look like ?
You don't have to use mapping, you can rely on elasticsearch's schemaless nature really well in this case.
The json serializer will write that out as:
{
"timestamp" : "[datestring]",
"measurement" : {
"visits" : 1,
"url" : [ "/help", "/about"]
}
}
You can query for the existence of the "measurement.visits" field like so using NEST.
var result = client.Search<Metric>(s=>s
.From(0)
.Size(10)
.Filter(filter=>filter
.Exists("measurement.visits")
)
);
result.Documents now hold the first 10 metrics with a visits key in the Measurement dictionary.
If you do want to explicitly map possible keys in that dictionary using the new fluent mapping:
var result = client.MapFluent<Metric>(m => m
.Properties(props => props
.Object<Dictionary<string,object>>(s => s
.Name(p => p.Measurement)
.Properties(pprops => pprops
.Number(ps => ps
.Name("visits")
.Type(NumberType.#integer)
)
.String(ps => ps
.Name("url")
.Index(FieldIndexOption.not_analyzed))
)
)
)
)
);
Remember that we haven't turned off dynamic mapping using this mapping so you can still inserts other keys into your dictionary without upsetting elasticsearch. Only now elasticsearch will know visits is an actual integer andwe dont want to analyze the url values.
since we are not using any typed accessors (The .Name() call is typed to Metric) .Object<Dictionary<string,object>> could be .Object<object> too.

Resources