Operator declares the comparison applied by a Condition. There are 28 operators total. Every operator that starts with I is the case-insensitive variant — both sides are normalized with .ToLower() before comparison. On PostgreSQL the six pattern variants can match with ILIKE instead, since 3.5.0 — see case-insensitive matching.
Required values at a glance
Each operator expects a specific number of entries in Condition.Values. Sending the wrong count throws a LogicException whose message is one of ConditionWithOperator[<op>]MustHasOnlyOneValue, ConditionWithOperator[Between-NotBetween]MustHasOnlyTwoValues, ConditionWithOperator[In-IIn-NotIn-INotIn]MustHasOneOrMoreValues, or ConditionWithOperator[IsNull-IsNotNull]MustHasNoValues. There is no error-code property — the string is the message.
Value count
Operators
0
IsNull, IsNotNull
1
All equality, contains, starts-with, ends-with, and ordered comparisons (20 operators total — the 28 minus the two null checks, the two range checks, and the four In variants).
2 (exactly)
Between, NotBetween
1+
In, IIn, NotIn, INotIn
Equality
Operator
Description
Required Values
Equal
Equality (case-sensitive for text).
1
IEqual
Equality (case-insensitive).
1
NotEqual
Inequality (case-sensitive).
1
INotEqual
Inequality (case-insensitive).
1
Contains / StartsWith / EndsWith
Operator
Description
Required Values
Contains
Text contains (case-sensitive).
1
IContains
Text contains (case-insensitive).
1
NotContains
Text does not contain (case-sensitive).
1
INotContains
Text does not contain (case-insensitive).
1
StartsWith
Starts with (case-sensitive).
1
IStartsWith
Starts with (case-insensitive).
1
NotStartsWith
Does not start with (case-sensitive).
1
INotStartsWith
Does not start with (case-insensitive).
1
EndsWith
Ends with (case-sensitive).
1
IEndsWith
Ends with (case-insensitive).
1
NotEndsWith
Does not end with (case-sensitive).
1
INotEndsWith
Does not end with (case-insensitive).
1
Note
Case-insensitive I* operators emit .ToLower() on both sides of the comparison. On SQL Server this is typically free (default collations are case-insensitive). On case-sensitive collations (e.g. PostgreSQL with C locale) this still works but may sidestep an index. On PostgreSQL, TextMatching.ILike lets a pg_trgm index serve IContains, IStartsWith, IEndsWith and their negations.
In / NotIn (set membership)
Operator
Description
Required Values
In
Value is in the set (case-sensitive for text).
1+
IIn
Value is in the set (case-insensitive).
1+
NotIn
Value is not in the set (case-sensitive).
1+
INotIn
Value is not in the set (case-insensitive).
1+
Long lists
A list of up to 32 values is written as one chain of comparisons, and a longer one as a balanced tree of such chains, which returns the same rows. Before 3.1.0 every list was one chain, and a single condition carrying about seven hundred values could overflow the request thread's stack and end the process — see breaking changes. Under ApplyPolicy, MaxConditionValues (default 1000) bounds the values one condition may carry.
Ordered comparisons & ranges
Operator
Description
Required Values
GreaterThan
Greater than.
1
GreaterThanOrEqual
Greater than or equal.
1
LessThan
Less than.
1
LessThanOrEqual
Less than or equal.
1
Between
Inclusive range — first value is the lower bound, second is the upper bound.
2 (exactly)
NotBetween
Outside the inclusive range defined by the two values.
2 (exactly)
Null checks
Operator
Description
Required Values
IsNull
Property is NULL.
0
IsNotNull
Property is NOT NULL.
0
On a date member that cannot be null
With DataType.Date or DataType.DateTime, the library reads the member's type first. A non-nullable DateTime, DateTimeOffset or DateOnly of the entity itself can never be null, so IsNull answers the constant false — no rows — and IsNotNull the constant true — every row. On PostgreSQL that is WHERE FALSE and no predicate at all. Reached through a navigation, as in Approval.ApprovedAt, they test the navigation instead: IsNull matches the rows with no Approval. On a nullable date member they test the column as usual. See breaking changes.
Warning
Sending any value with IsNull / IsNotNull throws ConditionWithOperator[IsNull-IsNotNull]MustHasNoValues. Send an empty array: "values": [].
New in 3.5.0. By default the I* operators lower both sides, field.ToLower().Contains("value"), which a relational provider writes as lower(column) LIKE …. A pg_trgm GIN or GiST index on the column cannot serve that, and it can serve column ILIKE '%value%'. How the case-insensitive operators match is one choice for the whole process, TextMatching, in DynamicWhere.ex.Enums; no request carries it.
Value
Meaning
Lower (0)
Both sides lowered. The default, unchanged, and what every provider and LINQ to objects can run.
ILike (1)
PostgreSQL's ILIKE, through Npgsql's EF.Functions.ILike, for IContains, INotContains, IStartsWith, INotStartsWith, IEndsWith and INotEndsWith.
A deployment on PostgreSQL chooses it once, at startup:
using DynamicWhere.ex.Enums;
using DynamicWhere.ex.Source;
DwText.Configure(o => o.CaseInsensitive = TextMatching.ILike);
// Or from configuration. A key nothing answers to — "CaseInsensitiv" — refuses to start,
// and so does a single value where the section belongs: "DynamicWhere:Text": "ILike".
DwText.Configure(new DwTextOptions().Bind(configuration.GetSection("DynamicWhere:Text")));
With ILike chosen, a condition on Name with the value "ab" compiles on Npgsql to:
Operator
SQL
IContains
i."Name" IS NOT NULL AND i."Name" ILIKE '%ab%' ESCAPE '\'
INotContains
i."Name" IS NOT NULL AND NOT (i."Name" ILIKE '%ab%' ESCAPE '\')
IStartsWith
i."Name" IS NOT NULL AND i."Name" ILIKE 'ab%' ESCAPE '\'
INotStartsWith
i."Name" IS NOT NULL AND NOT (i."Name" ILIKE 'ab%' ESCAPE '\')
IEndsWith
i."Name" IS NOT NULL AND i."Name" ILIKE '%ab' ESCAPE '\'
INotEndsWith
i."Name" IS NOT NULL AND NOT (i."Name" ILIKE '%ab' ESCAPE '\')
The predicate is built and parsed exactly as before, and the parsed lambda is then rewritten into EF.Functions.ILike, with a backslash as the escape character. The value is the one the condition sent, trimmed and lowered, with every backslash, % and _ in it escaped, so it matches as text and never as a pattern. A negation keeps its NOT, and the null guard stays in front. getQueryString shows the ILIKE SQL.
IEqual, INotEqual, IIn and INotIn stay lower(column) = value, which an index on lower(column) serves, and a Having condition stays lowered, since an aggregate uses no index. The case-sensitive operators are not affected.
It applies wherever the library filters through Where: a Filter, every set of a Segment and a Summary's row filter, guarded or not. Only the predicate the library parsed is rewritten; one the caller composed with LINQ beneath it is left as written.
Only a query EF Core's own provider translates is rewritten. LINQ to objects, the IEnumerable<T> overloads and a provider that wraps EF Core's, such as LinqKit's AsExpandable(), keep lowering.
Against PostgreSQL 16 it returns the same rows as lowering, and as LINQ to objects, for all six operators over ASCII values carrying %, _, backslashes and quotes, through the plain, guarded and segment paths.
The choice is the whole process's
Every EF Core query in the process is rewritten, whatever database it reaches. A process that also queries another database through EF Core — SQLite in tests, say — must not choose ILike: such a query fails translation with InvalidOperationException rather than match wrongly.
Member (DynamicWhere.ex.Source)
Description
DwText.Configure(Action<DwTextOptions>)
Fills in a fresh DwTextOptions and configures it.
DwText.Configure(DwTextOptions)
Checks and freezes the options and sets them for the process. Throws ArgumentNullException for null; ArgumentException for a value TextMatching does not define; InvalidOperationException when ILike is chosen and Npgsql.EntityFrameworkCore.PostgreSQL cannot be loaded — its EF.Functions.ILike is found by name, so the package references no provider and a deployment without it refuses to start — and InvalidOperationException on a later call asking for a different choice. A later call asking for the choice in force does nothing, so several hosts in one process, such as WebApplicationFactory hosts in one test run, can run the same startup. DwDates.Configure, by contrast, refuses every second call.
DwText.Options
The DwTextOptions in force. Frozen; Lower until a deployment configures otherwise.
DwText.IsConfigured
true once Configure has succeeded.
DwTextOptions.CaseInsensitive
TextMatching, Lower by default. Setting it once the options are frozen throws InvalidOperationException.
DwTextOptions.IsFrozen
true once the options have been handed to DwText.Configure.
DwTextOptions.Bind(IConfiguration)
Extension method. Reads CaseInsensitive from a section and returns the same instance; an absent section changes nothing. Throws InvalidOperationException for a key nothing answers to, for a value it cannot read as a TextMatching such as a misspelt "ILikee", for a single value where the section belongs ("DynamicWhere:Text": "ILike"), or when the options are already frozen.
Related
DataType → which operators each logical type accepts.