Elasticsearch Mappings
The ElasticMappingResolver provides intelligent field resolution based on Elasticsearch index mappings. It automatically handles analyzed vs non-analyzed fields, nested documents, and field types.
Overview
When you configure UseMappings(), the parser:
- Loads field mappings from your Elasticsearch index
- Resolves field names to their correct paths
- Automatically uses keyword sub-fields for sorting and aggregations
- Detects nested fields for proper query wrapping
- Identifies field types for appropriate query generation
Configuration
From Elasticsearch Client
var parser = new ElasticQueryParser(c => c
.UseMappings(client, "my-index"));From Type Mapping
var parser = new ElasticQueryParser(c => c
.UseMappings<MyDocument>(client));With Custom Mapping Builder
var parser = new ElasticQueryParser(c => c
.UseMappings<MyDocument>(
mappingBuilder: m => m
.Properties(p => p
.Text(t => t.Name(n => n.Title)
.Fields(f => f.Keyword(k => k.Name("keyword"))))
.Keyword(k => k.Name(n => n.Status))
.Date(d => d.Name(n => n.Created))
.Nested<Comment>(n => n.Name(x => x.Comments))),
client,
"my-index"));From Mapping Function
var parser = new ElasticQueryParser(c => c
.UseMappings(
getMapping: () => GetCachedMapping(),
inferrer: client.Infer));Field Resolution
Automatic Keyword Field Detection
For text fields with keyword sub-fields, the resolver automatically uses the keyword field for:
- Sorting
- Aggregations
- Exact match queries
// Mapping:
// "title": { "type": "text", "fields": { "keyword": { "type": "keyword" } } }
var parser = new ElasticQueryParser(c => c.UseMappings(client, "my-index"));
// For queries - uses analyzed "title" field
var query = await parser.BuildQueryAsync("title:search terms");
// For aggregations - automatically uses "title.keyword"
var aggs = await parser.BuildAggregationsAsync("terms:title");
// For sort - automatically uses "title.keyword"
var sort = await parser.BuildSortAsync("title");Field Type Detection
The resolver detects field types for appropriate query handling:
var resolver = parser.Configuration.MappingResolver;
// Check field types
bool isNested = resolver.IsNestedPropertyType("comments");
bool isGeo = resolver.IsGeoPropertyType("location");
bool isNumeric = resolver.IsNumericPropertyType("price");
bool isDate = resolver.IsDatePropertyType("created");
bool isBoolean = resolver.IsBooleanPropertyType("active");
bool isAnalyzed = resolver.IsPropertyAnalyzed("description");ElasticMappingResolver API
Getting Field Information
var resolver = parser.Configuration.MappingResolver;
// Get full field mapping
var mapping = resolver.GetMapping("user.name");
if (mapping.Found)
{
Console.WriteLine($"Full path: {mapping.FullPath}");
Console.WriteLine($"Property type: {mapping.Property?.GetType().Name}");
}
// Get the NEST property
IProperty property = resolver.GetMappingProperty("status");
// Get resolved field name
string resolved = resolver.GetResolvedField("user");
// Get non-analyzed field for sorting
string sortField = resolver.GetSortFieldName("title");
// Get non-analyzed field for aggregations
string aggField = resolver.GetAggregationsFieldName("category");
// Get field type enum
FieldType fieldType = resolver.GetFieldType("price");Field Type Enum
public enum FieldType
{
Unknown,
Text,
Keyword,
Date,
Boolean,
Long,
Integer,
Short,
Byte,
Double,
Float,
HalfFloat,
ScaledFloat,
GeoPoint,
GeoShape,
Nested,
Object,
// ... other types
}Nested Document Handling
Automatic Nested Query Wrapping
When UseNested() is enabled, queries on nested fields are automatically wrapped:
var parser = new ElasticQueryParser(c => c
.UseMappings(client, "my-index")
.UseNested());
// Query on nested field
var query = await parser.BuildQueryAsync("comments.author:john");
// Automatically generates:
// {
// "nested": {
// "path": "comments",
// "query": {
// "term": { "comments.author": "john" }
// }
// }
// }Nested Field Detection
var resolver = parser.Configuration.MappingResolver;
// Check if field is nested
bool isNested = resolver.IsNestedPropertyType("comments");
// Get the nested path for a field
// "comments.author" -> "comments"Mapping Extensions
Adding Keyword Sub-Fields
Use extension methods to add standard sub-fields to your mappings:
using Foundatio.Parsers.ElasticQueries.Extensions;
var createIndexResponse = await client.Indices.CreateAsync("my-index", c => c
.Map<MyDocument>(m => m
.Properties(p => p
// Add .keyword sub-field
.Text(t => t.Name(n => n.Title).AddKeywordField())
// Add .sort sub-field with lowercase normalizer
.Text(t => t.Name(n => n.Name).AddSortField())
// Add both .keyword and .sort sub-fields
.Text(t => t.Name(n => n.Description).AddKeywordAndSortFields())
)));Sub-Field Names
using Foundatio.Parsers.ElasticQueries.Extensions;
// Default sub-field names
string keywordField = ElasticMappingExtensions.KeywordFieldName; // "keyword"
string sortField = ElasticMappingExtensions.SortFieldName; // "sort"Sort Normalizer
Add a lowercase normalizer for case-insensitive sorting:
var createIndexResponse = await client.Indices.CreateAsync("my-index", c => c
.Settings(s => s.AddSortNormalizer())
.Map<MyDocument>(m => m
.Properties(p => p
.Text(t => t.Name(n => n.Name).AddSortField())
)));Refreshing Mappings
Mappings are automatically refreshed from Elasticsearch at most once per minute. In most production scenarios, this automatic refresh is sufficient.
For unit tests where you're creating or modifying indices and need immediate visibility of changes, you can force a refresh:
var resolver = parser.Configuration.MappingResolver;
// Force refresh from Elasticsearch (primarily for unit tests)
resolver.RefreshMapping();Custom Mapping Resolver
Create a custom resolver for special cases:
var customResolver = ElasticMappingResolver.Create(
getMapping: () => {
// Return cached or custom mapping
return _cachedMapping;
},
inferrer: client.Infer,
logger: logger);
var parser = new ElasticQueryParser(c => c
.UseMappings(customResolver));Field Mapping Structure
The FieldMapping class contains:
public class FieldMapping
{
// Whether the field was found in mappings
public bool Found { get; }
// The full resolved path (e.g., "data.user.name")
public string FullPath { get; }
// The NEST IProperty for the field
public IProperty Property { get; }
}Best Practices
1. Use Consistent Sub-Field Naming
// Always use .keyword for exact matching
// Always use .sort for case-insensitive sorting
.Text(t => t.Name(n => n.Title)
.Fields(f => f
.Keyword(k => k.Name("keyword").IgnoreAbove(256))
.Keyword(k => k.Name("sort").Normalizer("lowercase"))))2. Cache Mapping Resolution
The resolver caches mappings automatically, but you can also:
// Create resolver once and reuse
var resolver = ElasticMappingResolver.Create(client, "my-index");
var parser1 = new ElasticQueryParser(c => c.UseMappings(resolver));
var parser2 = new ElasticQueryParser(c => c.UseMappings(resolver));3. Handle Dynamic Mappings
For indices with dynamic mappings:
var parser = new ElasticQueryParser(c => c
.UseMappings(client, "my-index")
.SetValidationOptions(new QueryValidationOptions {
// Allow fields not in current mapping
AllowUnresolvedFields = true
}));4. Log Mapping Issues
var parser = new ElasticQueryParser(c => c
.SetLoggerFactory(loggerFactory)
.UseMappings(client, "my-index"));
// Mapping resolution issues will be loggedTroubleshooting
Field Not Found
var resolver = parser.Configuration.MappingResolver;
var mapping = resolver.GetMapping("unknown_field");
if (!mapping.Found)
{
// Field doesn't exist in mapping
// Check: spelling, case sensitivity, nested path
}Wrong Field Type Used
// Check what type the resolver sees
var fieldType = resolver.GetFieldType("my_field");
Console.WriteLine($"Field type: {fieldType}");
// Check if analyzed
bool isAnalyzed = resolver.IsPropertyAnalyzed("my_field");
Console.WriteLine($"Is analyzed: {isAnalyzed}");Nested Queries Not Working
// Ensure UseNested() is configured
var parser = new ElasticQueryParser(c => c
.UseMappings(client, "my-index")
.UseNested()); // Required for nested support
// Verify field is detected as nested
bool isNested = resolver.IsNestedPropertyType("comments");Next Steps
- Elasticsearch Integration - Full parser guide
- Query Syntax - Query syntax reference
- Aggregation Syntax - Aggregation reference