You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-**`ImmediateConsistency()` is for tests only**: It triggers an Elasticsearch index refresh after writes. Never use in production -- it degrades cluster performance.
351
364
-**Register repositories as singletons**: Repository instances maintain internal state (index configuration, cache references). Always register via DI as singletons.
352
365
-**`FieldEquals` with multiple values is OR**: `.FieldEquals(e => e.EmploymentType, "FullTime", "Contract")` produces an OR filter, not AND.
366
+
-**`FieldContains` is token matching, NOT wildcard**: `FieldContains(f => f.Name, "Er")` will NOT match "Eric". Use `FilterExpression("field:pattern*")` for prefix/wildcard matching.
367
+
-**`FieldNot` is AND-NOT**: Multiple conditions inside `FieldNot` mean NOT A AND NOT B. For NOT (A AND B), nest `FieldAnd` inside `FieldNot`.
368
+
-**Range operators + time-series indexes**: `FieldLessThanOrEqual(f => f.CreatedUtc, now)` does NOT narrow which daily/monthly indexes are queried. Always pair with `.Index(start, end)`.
369
+
-**`FieldEquals` on analyzed text fields throws**: If the field has no `.keyword` sub-field, `FieldEquals` throws `QueryValidationException`. Use `FieldContains` for full-text search.
353
370
-**`PatchAsync(Ids, ...)` requires `Ids` type**: Use `new Ids(id1, id2)` -- `string[]` does not implicitly convert to `Ids`.
> **String ranges require keyword fields:** String range operators generate a `TermRangeQuery` and automatically resolve to the `.keyword` sub-field (like `FieldEquals`). If the field is an analyzed text field with no `.keyword` sub-field, a `QueryValidationException` is thrown at build time.
121
+
122
+
**DateRange vs range operators:** Use `.DateRange(start, end, field)` for bounded date windows (validates start < end, supports timezone). Use `FieldGreaterThan`/`FieldLessThanOrEqual` etc. for one-sided comparisons and non-date types.
123
+
124
+
> **Numeric precision note:**`long` values use NEST's `LongRangeQuery` which preserves full precision. `decimal` values are converted to `double` for NEST's `NumericRangeQuery`, which may lose precision for values exceeding ~15-17 significant digits. If exact precision matters, prefer `long` fields with an explicit scaling factor.
125
+
126
+
### Contains (Full-Text Token Matching)
127
+
128
+
`FieldContains` generates a `MatchQuery` on analyzed fields. It matches complete analyzed tokens, NOT substrings or wildcards:
129
+
130
+
```csharp
131
+
// Matches documents where Name contains the token "eric"
132
+
varresults=awaitrepository.FindAsync(q=>q
133
+
.FieldContains(e=>e.Name, "Eric"));
134
+
135
+
// Multiple tokens: all must be present (AND), order-independent
136
+
// Matches "Eric J. Smith" AND "Smith, Eric" but NOT "Eric"
> **FieldNot semantics:** Multiple conditions inside `FieldNot` produce NOT A AND NOT B (exclude documents matching **any** clause). For NOT (A AND B), nest an explicit AND: `FieldNot(g => g.FieldAnd(g2 => g2.FieldEquals(A).FieldEquals(B)))`.
182
+
183
+
### Field-Type Validation
184
+
185
+
The query builder performs runtime validation and throws `QueryValidationException` for detectable misuse:
186
+
187
+
-**FieldEquals/FieldNotEquals on text-only fields** — throws if the field is an analyzed text field with no `.keyword` sub-field (TermQuery on analyzed fields almost never matches).
188
+
-**FieldContains/FieldNotContains on keyword fields** — throws because MatchQuery requires an analyzed field.
189
+
-**FieldEquals on IsDeleted with ActiveOnly soft-delete mode** — throws because it creates a contradictory filter.
190
+
-**Range with null value** — throws with guidance to use `*If` variants or `FieldHasValue`/`FieldEmpty`.
191
+
-**Range with collection value** — throws with guidance to use `FieldEquals` for multi-value matching.
192
+
193
+
> **Note:** For wildcard/prefix matching on keyword fields, use `FilterExpression("field:pattern*")`. For phrase matching (adjacent words in order), use `FilterExpression("field:\"quick brown\"")`.
92
194
93
-
> **Note:** For numeric range comparisons (e.g., age >= 25), use `FilterExpression` with Lucene syntax: `q.FilterExpression("age:[25 TO *]")`
0 commit comments