Skip to content
FlexQuery.NET

Operators

Every filter, HAVING condition, and expanded-branch filter in FlexQuery is built from one fixed set of operators. This page is the authoritative reference: the canonical names, the aliases that normalize into them, which .NET types each operator works with, how governance restricts them per field, and how each provider executes them.

Canonical operators

The parser normalizes every recognized operator to one of these canonical strings -- Status:EQ:Active and Status:eq:Active are the same query:

Comparison

OperatorMeaningWorks with
eqequalsscalars, strings, enums, dates, numbers, bools
neqnot equalsas above
gtgreater thannumbers, dates, comparable values
gtegreater than or equalas above
ltless thanas above
lteless than or equalas above

Text

OperatorMeaningNotes
containssubstring searchcase-sensitive in memory; collation-sensitive in SQL
startswithprefix matchstring properties only
endswithsuffix matchstring properties only
likeSQL-style pattern% = any run, _ = one char; executed through provider LIKE support

Sets and ranges

OperatorValue formatExample
incomma-separated listStatus:in:Active,Pending
notincomma-separated listStatus:notin:Cancelled,Refunded
betweentwo comma-separated bounds, inclusiveCreatedDate:between:2024-01-01,2024-02-01

Null checks

OperatorValueExample
isnullnoneDeletedAt:isnull
isnotnullnoneEmail:isnotnull

Collection operators

These target collection navigation paths; validation rejects them on scalar fields (NOT_A_COLLECTION / TYPE_MISMATCH):

OperatorMeaningExample
anyat least one related row matchesOrders:any:TotalAmount:gt:100
allevery related row matchesOrders.all:Status:eq:Delivered
countcount of related rows, compared to a valueOrders:count:gt:3, Orders.count(Status:eq:Pending):gte:2

any/all take their operand as a filter expression on the related type -- one nesting level down the same field:op:value grammar applies. The count form appends its comparison (:op:value) after the collection path.

Aliases

Every operator also accepts word aliases (and, where the operator arrives as its own string, symbolic ones):

CanonicalAliases
eqequal, equals; =, ==
neqne, notequal; !=, <>
gtgreaterthan; >
gtege, greaterthanorequal; >=
ltlessthan; <
ltele, lessthanorequal; <=
containscn
startswithstarts, sw
endswithends, ew
isnullnull
isnotnullnotnull, isnotempty
notinnot in

The symbolic aliases apply wherever an operator is parsed as a standalone string -- the JSON filters request model and governance sets. In the colon-separated DSL stick to the word forms (a bare Status=x is not a DSL filter at all -- the operator segment lives between colons).

Operator names are normalized to the canonical string before governance checks and before execution, so an AllowedOperators entry written as gte also admits ge.

Type rules

The validator enforces operator/property-type compatibility before the query runs:

  • contains, startswith, endswith, like require string properties.
  • gt, gte, lt, lte, between require comparable types; values must convert to the property type (TYPE_MISMATCH otherwise -- a gt on a decimal never falls back to string ordering).
  • in / notin items each convert to the property type.
  • isnull / isnotnull take no value.
  • any, all, count address collection navigations; dotted paths through them apply to the nested type's members.

Unknown operators fail with INVALID_OPERATOR; disallowed-by-governance operators with OPERATOR_NOT_ALLOWED -- both before SQL is produced.

Execution by provider

OperatorEF CoreDapperIn memory
comparisonsSQL predicates, parameterizedparameterized SQLexpression-tree comparisons
contains / startswith / endswithprovider LIKE/string translationdialect LIKE with pattern constructionordinal string.Contains etc.
likeEF.Functions.Likedialect LIKEpattern translation applied by the engine
in / notinlist parameterizationparameterized list / NOT INContains closures
between>= AND <=>= AND <=two comparisons
isnull / isnotnullIS [NOT] NULLIS [NOT] NULLnull checks
anycorrelated EXISTSEXISTS subquery.Any(...)
allNOT EXISTS(NOT ...)NOT EXISTS subquery.All(...)
countscalar count subquerySELECT COUNT(...) predicate.Count() compared

Two semantics worth knowing:

  • all compiles to a double-negated NOT EXISTS, so an entity with no related rows passes an all check (vacuous truth, matching SQL).
  • Text comparisons are ordinal in memory and collation-dependent in EF Core/Dapper -- case behavior follows the database.

Operator governance

Per-field allow-lists are keyed by field (case-insensitive) and hold the canonical operator strings:

C#
opt.AllowedOperators = new(StringComparer.OrdinalIgnoreCase)
{
    ["Status"] = ["eq", "in", "notin"],
    ["Age"]    = ["gt", "gte", "lt", "lte", "between"],
    ["City"]   = ["eq"],
};

Clients on City then get OPERATOR_NOT_ALLOWED for City:contains:ber; the request never reaches the database.

Where operators appear

  1. The root filter parameter (Filtering).
  2. expand branch filters (Orders(all:Status:eq:Shipped; take=5) -- see Expand).
  3. HAVING conditions, restricted to eq ne gt gte lt lte comparisons over declared aggregate values (Grouping & Aggregates).
  4. The flat filters condition list on FlexQueryRequest ({ "field": ..., "operator": ..., "value": ... }).