Skip to content

Configuration ​

This guide covers configuration options for the various components of Foundatio.Lucene.

Core Parser ​

The core parser is stateless and requires no configuration:

csharp
var result = LuceneQuery.Parse("title:hello AND status:active");

LuceneQuery.Parse is resilient: it always returns a (possibly partial) AST plus any ParseErrors, and bounds nesting depth (LuceneParser.MaxDepth, default 100) so deeply nested input can never overflow the stack.

Entity Framework Parser ​

csharp
var parser = new EntityFrameworkQueryParser(config =>
{
    config.SetDefaultFields("Name", "Email");
    config.SetDefaultOperator(BooleanOperator.And);
    // Controls 'now' in date math (now-7d, now/d). Defaults to TimeProvider.System.
    config.SetTimeProvider(TimeProvider.System);
});

var filter = parser.BuildFilter<Employee>("name:john AND salary:[50000 TO *]");
var employees = context.Employees.Where(filter).ToList();

Field aliasing, includes and date math (per request) ​

Field maps, @include references and date math are applied through the shared visitor pipeline, exactly as in the Elasticsearch integration. Supply them per request via EntityFrameworkQueryOptions (the recommended way to swap configuration per scope/tenant):

csharp
var options = new EntityFrameworkQueryOptionsBuilder()
    .WithFieldMap(new FieldMap { { "user", "Name" }, { "dept", "Department.Name" } })
    .WithIncludes(new Dictionary<string, string> { ["seniors"] = "Title:Senior" })
    .WithValidationOptions(o => o.AllowLeadingWildcards = false)
    .Build();

var filter = parser.BuildFilter<Employee>("user:john AND @include:seniors", context: null, options);

You can also register default options per entity type with parser.SetOptions<Employee>(options). Per-request options take precedence over registered options, which take precedence over the global configuration.

SQL cannot honor fuzzy (term~N) or proximity/slop ("a b"~N) queries. Rather than silently returning the wrong rows, the EF builder throws QueryBuildException (use TryBuildFilter to get a QueryResult instead of an exception). Regex queries depend on the database provider.

Elasticsearch Parser ​

csharp
var parser = new ElasticsearchQueryParser(config =>
{
    config.UseScoring = true;                         // match queries (scoring) vs term queries (filter)
    config.DefaultFields = ["title", "content"];      // fields for unfielded terms
    config.DefaultOperator = BooleanOperator.And;     // implicit operator
    config.FieldMap = new FieldMap { { "author", "metadata.author" } };
    config.IsDateField = field => field.EndsWith("At") || field.EndsWith("date");
    config.DefaultTimeZone = "America/Chicago";
    config.ValidationOptions = new QueryValidationOptions { AllowLeadingWildcards = false };
});

var query = parser.BuildQuery("author:john AND status:active");

Configuration properties ​

PropertyTypeDefaultDescription
UseScoringboolfalseUse match queries (scoring) vs term queries (filtering)
DefaultFieldsstring[]?nullFields to search for unfielded terms
DefaultOperatorBooleanOperatorOrDefault boolean operator for implicit combinations
FieldMapFieldMap?nullField name mappings
IncludesIReadOnlyDictionary<string,string>?nullPre-resolved @include content
IsDateFieldFunc<string, bool>?nullDetects date fields for date range queries
DefaultTimeZonestring?nullDefault timezone for date range queries
ValidationOptionsQueryValidationOptions?nullQuery validation options
TimeProviderTimeProviderSystemControls now in date math

Per-request / per-scope options ​

Swap configuration per scope by passing a prebuilt ElasticsearchQueryOptions (or registering it per index name). Options are immutable records, so an application can cache one per tenant and reuse it across requests:

csharp
var tenantOptions = new ElasticsearchQueryOptionsBuilder()
    .WithFieldMap(new FieldMap { { "user", "tenant42.userName" } })
    .WithDefaultFields("tenant42.title")
    .Build();

var query = parser.BuildQuery("user:john", tenantOptions);

The same parser instance is thread-safe and reuses its visitors across requests, so swapping options per request does not allocate a visitor per call.

Field Map ​

csharp
var fieldMap = new FieldMap
{
    { "user", "account.username" },
    { "created", "metadata.createdAt" }
};

FieldMap is case-insensitive. By default ResolutionMode is Hierarchical, so nested paths resolve by longest matching prefix:

csharp
var fieldMap = new FieldMap
{
    { "data", "payload" },
    { "data.user", "payload.account.username" }
};
// "data.user:john"   -> "payload.account.username:john"
// "data.status:open" -> "payload.status:open"

Set ResolutionMode = FieldResolutionMode.Direct for exact-match-only resolution, ReportUnmappedFields = true to flag unmapped fields as unresolved, or ResultPrefix to prefix every resolved name. The parsers apply the field map automatically; to run it manually:

csharp
FieldResolverQueryVisitor.Run(result.Document, fieldMap);

Includes ​

@include:name references expand from a pre-resolved dictionary (resolve saved queries from your store before parsing):

csharp
var includes = new Dictionary<string, string> { ["recent"] = "created:[now-7d TO now]" };

var parser = new ElasticsearchQueryParser(c => c.Includes = includes);
var query = parser.BuildQuery("@include:recent AND status:active");

// Or run it manually against the AST:
result.Document.ExpandIncludes(includes);

Circular references and excessive nesting (IncludeVisitor.MaxIncludeDepth) are detected and reported as validation errors.

Validation ​

csharp
var options = new QueryValidationOptions
{
    AllowLeadingWildcards = false,
    AllowWildcardOnlyQueries = false,
    AllowedFields = { "title", "author", "status", "date" },
    DisallowedFields = { "password", "ssn" }
};

var result = QueryValidator.ValidateQuery("title:hello", options);
if (!result.IsValid)
{
    // result.ValidationErrors describe what failed
}
PropertyTypeDefaultDescription
AllowLeadingWildcardsbooltrueAllow patterns like *suffix
AllowWildcardOnlyQueriesbooltrueAllow * or *:* queries
AllowedFieldsHashSet<string>emptyWhitelist of allowed fields
DisallowedFieldsHashSet<string>emptyBlacklist of disallowed fields

Custom Visitors ​

Compose visitors with ChainedQueryVisitor and run them synchronously:

csharp
var visitors = new ChainedQueryVisitor()
    .AddVisitor(new FieldResolverQueryVisitor(fieldMap), priority: 10)
    .AddVisitor(new DateMathEvaluatorVisitor(), priority: 30)
    .AddVisitor(new ValidationVisitor(), priority: 100);

var context = new QueryVisitorContext();
visitors.Accept(result.Document, context);

Dependency Injection ​

csharp
services.AddSingleton<EntityFrameworkQueryParser>();

services.AddSingleton(sp => new ElasticsearchQueryParser(config =>
{
    config.UseScoring = true;
    config.DefaultFields = ["title", "content"];
    config.ValidationOptions = new QueryValidationOptions { AllowLeadingWildcards = false };
}));

Next Steps ​

Released under the Apache 2.0 License.