ElasticLINQ icon indicating copy to clipboard operation
ElasticLINQ copied to clipboard

Return complex array/list on select statement.

Open rafamerlin opened this issue 7 years ago • 0 comments

Hello Damien, This is a similar scenario from one of my other issues (https://github.com/ElasticLINQ/ElasticLINQ/issues/110) but this one is a bit more complicated.

Let's imagine we have this classes:

public class Customer {
    public string Name {get;set;}
    public bool Active {get;set;}
    public List<Address> Address {get;set;}
}

public class Address {
    public string Street {get;set;}
    public string Suburb {get;set;}
}

If my ES document matches this, when If I run a query like this:

var result = context.Query<Customer>
                   .Select(x => new {Name = x.Name, Address = x.Address});

I'll get the following issue: ElasticsearchIllegalArgumentException[field [address] isn't a leaf field];

I've worked around it by doing this:

public class Customer {
    public string Name {get;set;}
    public bool Active {get;set;}
    [JsonProperty(PropertyName = "address.street")]
    public string[] AddressStreet {get;set;}
    [JsonProperty(PropertyName = "address.suburb")]
    public string[] AddressSuburb {get;set;}
    public List<Address> Address {get;set;}
}

var result = context.Query<Customer>
                   .Select(x => new {Name = x.Name, AddressStreet = x.AddressStreet, AddressSuburb = x.AddressSuburb });

Then after I materialize the query (using the .ToList() for example) I assemble the Address list by looping through the arrays in AddressStreet and AddressSuburb (as they should always have the same size).

Checking the generated query against ES, it generates this:

{
	"fields": ["name", "active", "address.street", "address.suburb"],
	"timeout": "30s"
}

So the hits in the results should be similar to this:

{
	"hits": {
		"total": 1,
		"max_score": 1,
		"hits": [{
				"_index": "exampleIndex",
				"_type": "exampleType",
				"_id": "1",
				"_score": 1,
				"fields": {
					"name": [
						"Fake Name"
					],
					"address.street": [
						"address1",
						"address2",
						"address3",
					],
					"address.suburb": [
						"suburb1",
						"suburb2",
						"suburb3"
					]
				}
			}
		]
	}
}

So even fields that are not arrays are returned as arrays (like Name).

If I change manually this request to use _source instead of fields:

{
	"_source": ["name", "active", "address.street", "address.suburb"],
	"timeout": "30s"
}

This is the result:

{
	"hits": {
		"total": 1,
		"max_score": 1,
		"hits": [{
				"_index": "exampleIndex",
				"_type": "exampleType",
				"_id": "1",
				"_score": 1,
				"_source": {
					"name": "Fake Name",
					"address": [
						{
							"street": "street1",
							"suburb": "suburb1"
						}, {
							"street": "street2",
							"suburb": "suburb2"
						}, {
							"street": "street3",
							"suburb": "suburb3"
						}
					]
				}
			}
		]
	}
}

So what changes is that in the result the "fields" for each result is replaced by "_source" and that now the address object is generated as an object, also another important detail is that Name is not returned as an Array anymore, it's just a regular field.

What I would like to know is if it's possible to change field to _source in ElasticLinq and maybe if these different return types (returning objects and also not returning arrays for single fields) would cause too much problems regarding these changes (if they are possible in the first place).

Edit: Thinking about it I reckon it would break cases similar to the https://github.com/ElasticLINQ/ElasticLINQ/issues/110 as instead of returning a String for example it would return an object instead. Which could break implementations that are already in place for similar scenarios.

So maybe it would be better instead of replacing the behaviour to maybe add a parameter when getting the context of something to be able to use _source instead of field, while keeping the field as well available for simpler cases.

rafamerlin avatar Oct 31 '17 06:10 rafamerlin