All, I am playing around with ElasticSearch 6.x with NEST and for simplicity, I created mappings with the following POCO settings based on the NEST 6.x documentation provided here https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/parent-child-relationships.html. My data model is a simple one with
Customer
Order
Package
OrderItem
Here is the C# POCO settings
[ElasticsearchType(Name = "customer")] public class Customer { [PropertyName("customerId")] public int CustomerId { get; set; } [PropertyName("firstName")] [Text] public string FirstName { get; set; } [PropertyName("lastName")] [Text] public string LastName { get; set; } [PropertyName("email")] [Keyword] public string Email { get; set; } [PropertyName("customer_join_field")] public JoinField CustomerJoinField { get; set; } } [ElasticsearchType(Name = "order")] public class Order : Customer { [PropertyName("orderId")] public int OrderId { get; set; } [PropertyName("orderAmount")] public decimal Amount { get; set; } [Nested] [PropertyName("packages")] public List<Package> Packages { get; set; } [Nested] [PropertyName("orderItems")] public List<OrderItem> OrderItems { get; set; } } public class Package { public int PackageId { get; set; } public int Qty { get; set; } public int OrderId { get; set; } public string Weight { get; set; } } public class OrderItem { public int OrderItemId { get; set; } public int Quantity { get; set; } public decimal UnitPrice { get; set; } }
Building the ES client
public static ElasticClient ESClient
{
get
{
var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(connectionPool)
.DefaultMappingFor<Customer>(i => i
.IndexName("fa")
.TypeName("customer"))
.DefaultMappingFor<Order>(i => i
.IndexName("fa")
.TypeName("customer"))
.EnableDebugMode()
.PrettyJson()
.RequestTimeout(TimeSpan.FromMinutes(2));
return new ElasticClient(settings);
}
}
Configuring the Index(fa)
public static void ConfigureIndex()
{
try
{
var createIndexResponse = ESClient.CreateIndex("fa", c => c
.Index<Customer>()
.Mappings(ms => ms
.Map<Customer>(m => m
.RoutingField(r => r.Required())
.AutoMap<Customer>()
.AutoMap<Order>()
.Properties(props => props
.Join(j => j
.Name(p => p.CustomerJoinField)
.Relations(r => r
.Join<Customer, Order>()
)
)
)
)
)
);
}
Adding the customer and order types( Under the same customer type, since an index can have only one type in ES 6.x)
public static void AddCustomerDocument(Customer cust)
{
try
{
var result = ESClient.Index<Customer>(cust,
c => c
.Id(cust.CustomerId)//to avaoid random Ids
.Routing(cust.CustomerId)
);
//var response = ESClient.Index(cust, i => i.Routing(Routing.From(cust)));
}
catch (Exception ex)
{
throw;
}
}
public static void AddOrderDocument(Order order)
{
try
{
var result = ESClient.Index<Customer>(order,
c => c
.Id(order.OrderId)//to avaoid random Ids
.Routing(order.CustomerId)
);
//var response = ESClient.IndexDocument<Order>(order);
}
catch (Exception ex)
{
throw;
}
}
Generated mapping in ES is
{
"fa": {
"mappings": {
"customer": {
"_routing": {
"required": true
},
"properties": {
"customerId": {
"type": "integer"
},
"customer_join_field": {
"type": "join",
"eager_global_ordinals": true,
"relations": {
"customer": "order"
}
},
"email": {
"type": "keyword"
},
"firstName": {
"type": "text"
},
"lastName": {
"type": "text"
},
"orderAmount": {
"type": "double"
},
"orderId": {
"type": "integer"
},
"orderItems": {
"type": "nested",
"properties": {
"orderItemId": {
"type": "integer"
},
"quantity": {
"type": "integer"
},
"unitPrice": {
"type": "double"
}
}
},
"packages": {
"type": "nested",
"properties": {
"orderId": {
"type": "integer"
},
"packageId": {
"type": "integer"
},
"qty": {
"type": "integer"
},
"weight": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
}
}
}
I am querying this index to get a specific orderItem under a specific customer(CustomerId= 1) with the unitprice 12.23.
This is the Query DSL I have
{
"query":{
"parent_id":{
"type":"order",
"id":"1"
},
"nested":{
"path":"orderItems",
"score_mode":"avg",
"query":{
"bool":{
"must":[
{"match":{"orderItems.unitPrice" : "12.23"}}
]
}
}
}
}
}
When I do this, I get the below eror message
{
"error": {
"root_cause": [
{
"type": "parsing_exception",
"reason": "[parent_id] malformed query, expected [END_OBJECT] but found [FIELD_NAME]",
"line": 9,
"col": 4
}
],
"type": "parsing_exception",
"reason": "[parent_id] malformed query, expected [END_OBJECT] but found [FIELD_NAME]",
"line": 9,
"col": 4
},
"status": 400
}
My question is
Is it possible(or recommended) to do both parent child relationship and nested types in ES 6.x?
If not, are my options limited to denormalizing the the models( have package, OrderItems be the children of Customer, rather than Order and add fields in Package and OrderItems? ) or do multiple queries against ES and move the logic of flattening the data format I want to application side?
This is the sample data I've created ( localhost:9200/fa/customer/_search)
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 3,
"max_score": 1,
"hits": [
{
"_index": "fa",
"_type": "customer",
"_id": "1",
"_score": 1,
"_routing": "1",
"_source": {
"customerId": 1,
"firstName": "Rennish",
"lastName": "Joseph",
"email": "[email protected]",
"customer_join_field": "customer"
}
},
{
"_index": "fa",
"_type": "customer",
"_id": "100",
"_score": 1,
"_routing": "1",
"_source": {
"orderId": 100,
"orderAmount": 23.45,
"packages": [
{
"packageId": 1,
"qty": 2,
"orderId": 1,
"weight": "2.3"
},
{
"packageId": 2,
"qty": 1,
"orderId": 1,
"weight": "2.5"
}
],
"orderItems": [
{
"orderItemId": 1,
"quantity": 2,
"unitPrice": 12.23
},
{
"orderItemId": 2,
"quantity": 1,
"unitPrice": 10.23
}
],
"customerId": 1,
"customer_join_field": {
"name": "order",
"parent": "1"
}
}
},
{
"_index": "fa",
"_type": "customer",
"_id": "101",
"_score": 1,
"_routing": "1",
"_source": {
"orderId": 101,
"orderAmount": 23.45,
"packages": [
{
"packageId": 1,
"qty": 2,
"orderId": 1,
"weight": "2.3"
},
{
"packageId": 2,
"qty": 1,
"orderId": 1,
"weight": "2.5"
}
],
"orderItems": [
{
"orderItemId": 1,
"quantity": 2,
"unitPrice": 12.23
},
{
"orderItemId": 2,
"quantity": 1,
"unitPrice": 10.23
}
],
"customerId": 1,
"customer_join_field": {
"name": "order",
"parent": "1"
}
}
}
]
}
}