To get started you need to create an ElasticConnection with the URL and timeout values (and optionally index name), e.g.
var connection = new ElasticConnection(new Uri("http://192.168.2.12:9200"), Index: "myIndex");
Then create an ElasticContext with the connection passed in the constructor and the mapping provider to use:
var context = new ElasticContext(connection, new CouchbaseElasticMapping());
Now you are ready to execute queries:
var results = context.Query<Customers>().Where(c => c.EndDate < DateTime.Now);
A number of the basic operations are supported including:
Skip(n)
maps to lengthTake(n)
maps to countSelect(...)
may map to fields (see below)Where(...)
may map to filter or query (see below)OrderBy
,OrderByDescending
maps to sort (see below)ThenBy
,ThenByDescending
maps to sort desc (see below)First
,FirstOrDefault
maps to length 1, filter if predicate suppliedSingle
,SingleOrDefault
maps to length 2, filter if predicate suppliedSum
,Average
,Min
,Max
map to facetsGroupBy
causes facets to switch between termless and termed facetsCount
,LongCount
maps to count query if top level, facet if within a GroupBy
Select is supported and detects whole entity vs field selection with field, anonymous object and Tuple creation patterns:
Examples of supported whole-entity selects:
Select()
Select(r => r)
Select(r => new { r })
Select(r => Tuple.Create(r))
Select(r => new { r, ElasticFields.Score })
Select(r => Tuple.Create(r, ElasticFields.Score, r.Name)
Examples of supported name-field selects:
Select(r => r.Name)
Select(r => new { r.Name })
Select(r => Tuple.Create(r.Name))
Select(r => new { r.Name, ElasticFields.Score })
Select(r => Tuple.Create(r.Id, ElasticFields.Score, r.Name)
Where creates filter operations and supports the following patterns:
< > <= >=
maps to range==
maps to term!=
maps to term inside a not||
maps to or&&
maps to andHasValue
,!=null
maps to exists!HasValue
,==null
maps to missingEquals
for static and instance maps to termContains
on IEnumerable/array maps to terms
To create similar expression as queries use the .Query extension operator. It maps very similar operations but exists and missing are not available within queries on Elasticsearch.
Ordering is achieved by the usual LINQ methods however if you wish to sort by score you have two options:
- Normal method with ElasticFields.Score static property
OrderBy(o => ElasticFields.Score)
- IQueryable methods with score in the name
OrderByScore()
The latter is more easily discovered but the former should be kept around as it is the same pattern used in Select projections. Recommend the former for the less common orderings and latter for common.
There are three different ways you can perform aggregate operations like Sum, Count, Min, Max and Average:
If you want aggregates broken down by a field:
db.Query<Robot>().GroupBy(r => r.Zone).Select(g => new { Zone = g.Key, Count = g.Count() });
If you want one aggregate for the entire set:
db.Query<Robot>().Count();
We also support a less well-known operation that lets you retrieve multiple aggregates for the set in a single hit using GroupBy and a constant value:
db.Query<Robot>().GroupBy(r => 1).Select(g => new { Count = g.Count(), Sum = g.Sum(r => r.Cost) });
Where Elasticsearch exceeds the basic LINQ patterns some additional extensions are provided to expose that functionality.
####Extensions
A number of extensions on IQueryable are available via ElasticQueryExtensions
to provide some of this functionality, this includes:
OrderByScore
,OrderByScoreDescending
,ThenByScore
,ThenByScoreDescending
to order by the _score field.Query
to specify query criteria the same wayWhere
maps to filter criteria.QueryString(query)
to pass through a query_string for search.
####ElasticFields There is a static class called ElasticFields. This currently provides just Score and Id properties but you can use these to stand-in for the _score and _id values in Elasticsearch, e.g:
Select(c => new { c, ElasticFields.Score })
wraps the entity with its score.
OrderBy(c => ElasticFields.Score)
orders results by score.
There is a mapping interface called IElasticMapping. A default CouchbaseElasticMapping is provided that maps against the current structure shown by Tier 3.
JSON.Net is required for converting the Elasticsearch queries to JSON and parsing the results coming back.
XUnit.Net is required for the unit tests.
NSubstitute is required for some unit tests.
Currently around 95% test coverage and some tests use an included HTTP listener to correct sending and receiving of requests.
The query translator supports a few query optimizations to ensure that the generated ElasticLINQ query looks good and not like it was translated from another language.
This includes currently:
- Combining multiple == for same field in same term
- Combining multiple < > <= >= for same field into single range
- Combining OR and AND expression trees into flattened ORs and ANDs
- Cancelling out NOT when inside operation can be inverted
- Formatting non-string values for querying - e.g. dates
- Error reporting for unsupported LINQ syntax
- Error reporting for bad or incorrect return data
- Async
- Caching
- Hooks for profiling
- Any other desired Elasticsearch operations
Nested queries running multiple Elasticsearch requests
Very complex to do and breaks the explicit boundaries recommended in LINQ providers.