DynamicWhere.ex
DynamicWhere.exv3.4.0·docs

Condition Validation

A Condition is validated before its predicate is generated. Any broken rule throws a LogicException with the listed error code.

Rules

RuleError Code
Field must be non-empty and exist on TInvalidField
Field's first segment must not be a name the expression parser keeps for itself — new, iif, np, isnull, is, as, cast, true, false, null, in any letter caseFieldPath[{path}]StartsWithReservedName
Between / NotBetween require exactly 2 valuesRequiredTwoValue
In / IIn / NotIn / INotIn require 1+ valuesRequiredValues
IsNull / IsNotNull require 0 valuesNotRequiredValues
All other operators require exactly 1 valueRequiredOneValue({Operator})
A null value normalizes to an empty string — accepted by Text and Enum, rejected by every other DataTypeInvalidFormat
Guid values must parse as GuidInvalidFormat
Number values must be a literal the expression parser reads, and one it can compare with the member the condition namesInvalidFormat
Boolean values must parse as boolInvalidFormat
Date / DateTime values must be ISO 8601, year-first, or a format declared with DwDates.ConfigureAmbiguousDateFormat for a day/month-first date, otherwise InvalidFormat
A field the parser would read as its own is refused before the lookup
The expression parser reads its own functions and literals before it looks for a member, so a path whose first segment is one of them never reaches the member. New in 3.1.0: the library refuses it by name, with the first segment — trimmed — on LogicException.Subject. The check sits where any path is validated, so Orders, Selects, GroupBy.Fields, AggregateBy.Field and a [DwEntity(DefaultOrder)] entry answer the same way, guarded or not. Only the first segment counts — Owner.New names the member — and it, root, parent and every predefined type name such as String or Guid are ordinary members. Rename the CLR property and map the column with [Column]. See breaking changes.
A number value is read the way the parser will read it (3.3.0)
The predicate builder writes a Number value into the expression unquoted, exactly as sent, so validation reads it as the expression parser reads it and not as the host's culture does. First the parser's grammar, in the invariant culture: optional white space, an optional minus, digits, an optional fraction with a digit on both sides of the point, an optional exponent. No leading plus, no thousands separator, no trailing sign, no NaN and no Infinity; an integer must fit UInt64, or Int64 when negative. Then, in a Where condition, whether that literal compares with the member the condition names — the parser itself is asked, so 1.5 is refused on an int?, 1e-7 on a decimal, and any number on a string. A Having condition reads the grammar and stops, because an alias has no member type to ask about. Until 3.3.0 the check was TryParse in the host's culture and such values passed, then threw ParseException when the query was built. See breaking changes.
A date value is read the way the predicate will read it
Validation reads a date value exactly as the predicate builder does, with the member's own DateTime, DateTimeOffset or DateOnly type. The server's culture plays no part, so a value is accepted or refused the same way on every host, and a value that passes validation is one the builder can use. A deployment that declares a local format is no different: a format whose own text the built-in readers also read is refused when it is configured, so no declaration can make a value depend on the host's time zone. ISO 8601 ("2026-09-15", "2026-09-15T12:00:00Z") and year-first dates are accepted everywhere; "01/09/2026" is refused with AmbiguousDateFormat unless the deployment declares its order. See DataType and breaking changes.
Under a strict policy, an unknown field is a policy refusal
On a query guarded by ApplyPolicy under the Strict tier, outside a dry run, a Field that names nothing on T does not raise InvalidField. It is refused like a field denied for every feature — a PolicyException with FieldDeniedForWhere, or FieldDeniedForSegment inside a segment, and FieldPath "*" — so the answer does not say whether the field exists. Unguarded queries, the convenience tier and a dry run raise InvalidField as before. See what a strict refusal says.
Note
Operator/value arity is enforced before any value is parsed. A missing value for Between raises RequiredTwoValue regardless of whether the (missing) value would have parsed. There is no separate "value is null or whitespace" failure: InvalidValue (ConditionValuesAreNullOrWhiteSpace) is defined but never thrown.