Skip to main content

Reference

Reference

Both overloads are in Vali_Flow.NoSql.Elasticsearch.Extensions.ValiFlowElasticsearchExtensions.

ToElasticsearch<T>(this ValiFlow<T> flow, Func<object?, FieldValue?>? customConverter = null)

Translates the conditions accumulated in a ValiFlow<T> builder into an Elasticsearch Query.

ParameterTypeRequiredDescription
flowValiFlow<T>YesThe builder containing the conditions.
customConverterFunc<object?, FieldValue?>?NoHook for mapping CLR types not handled by the built-in switch. Return null to fall through to the default conversion.

Returns: Query — pass directly to Search, Count, DeleteByQuery, etc.

ToElasticsearch<T>(this Expression<Func<T, bool>> expression, Func<object?, FieldValue?>? customConverter = null)

Same translation for a pre-built Expression<Func<T, bool>>.

Expression<Func<Product, bool>> expr = p => p.Category == "Electronics" && p.Price < 500m;
Query filter = expr.ToElasticsearch();

Supported Operations

ValiFlow methodIR nodeElasticsearch query
.EqualTo(x => x.Field, v)EqualNode(IsNegated: false)term { field: value }
.NotEqualTo(x => x.Field, v)EqualNode(IsNegated: true)bool { must_not: [term { field: value }] }
.GreaterThan(x => x.Field, v)ComparisonNode(GT)range { field: { gt: value } }
.GreaterThanOrEqualTo(x => x.Field, v)ComparisonNode(GTE)range { field: { gte: value } }
.LessThan(x => x.Field, v)ComparisonNode(LT)range { field: { lt: value } }
.LessThanOrEqualTo(x => x.Field, v)ComparisonNode(LTE)range { field: { lte: value } }
.Contains(x => x.Field, "txt")LikeNode(Contains)wildcard { field: "*txt*", case_insensitive: true }
.StartsWith(x => x.Field, "pre")LikeNode(StartsWith)wildcard { field: "pre*", case_insensitive: true }
.EndsWith(x => x.Field, "suf")LikeNode(EndsWith)wildcard { field: "*suf", case_insensitive: true }
.In(x => x.Field, list)InNodeterms { field: [...] }
.IsNull(x => x.Field)NullNode(IsNull)bool { must_not: [exists { field }] }
.IsNotNull(x => x.Field)NullNode(IsNotNull)exists { field }
.And(a, b)AndNodebool { must: [a, b] }
.Or(a, b)OrNodebool { should: [a, b], minimum_should_match: 1 }
.Not(inner)NotNodebool { must_not: [inner] }

Range queries use NumberRangeQuery, which requires a numeric field. The value is converted to double via Convert.ToDouble. Passing a non-numeric value to a range comparison throws NotSupportedException.

Wildcard special characters (*, ?, \) in pattern strings are escaped before building the Elasticsearch pattern.


Type Mapping

The built-in ToFieldValue switch handles these CLR types for term and terms queries:

CLR typeFieldValue produced
nullFieldValue.Null
boolFieldValue.Boolean(b)
intFieldValue.Long(i)
longFieldValue.Long(l)
doubleFieldValue.Double(d)
floatFieldValue.Double(f)
decimalFieldValue.Double((double)dec)
stringFieldValue.String(s)
EnumFieldValue.Long(Convert.ToInt64(e))
anything elseFieldValue.String(v.ToString())

Custom Value Converter

Use customConverter when your domain types are not in the table above, or when you need a different FieldValue representation than the default.

The converter is called before the built-in switch. Return null to let the default handle the value.

// Domain type
record Money(decimal Amount, string Currency);

// Converter: represent Money as a double FieldValue
Query filter = new ValiFlow<Product>()
.LessThan(x => x.Price, new Money(500m, "USD"))
.ToElasticsearch(value =>
{
if (value is Money m)
return FieldValue.Double((double)m.Amount);
return null;
});

Forcing decimal to use string representation in a keyword field:

Query filter = new ValiFlow<Invoice>()
.EqualTo(x => x.TaxRate, 0.21m)
.ToElasticsearch(value =>
{
if (value is decimal d)
return FieldValue.String(d.ToString("G", CultureInfo.InvariantCulture));
return null;
});

Advanced Example

var query = new ValiFlow<Product>()
.IsNotNullOrEmpty(x => x.Category)
.GreaterThan(x => x.Price, 25m)
.ToElasticsearch();