Summary#
Field search ranks an entity's own rows by a query against one of its [Searchable(Entity)] fields (see
[Searchable]). Three primitives give you the pieces, and you fuse them yourself:
Prop.Matches(q)→bool— does the field match the keyword query? (the indexed candidate filter)Prop.TextScore(q)→decimal— full-text relevance (higher = better keyword match)Prop.Similarity(q)→decimal— semantic similarity (higher = closer in meaning)
Each returns a value, never a magic ranking. You compare, order, and weight them with ordinary expressions, so you decide what "relevant enough" means — the compose-your-own-hybrid idiom below.
Signature#
bool Prop.Matches(string q) // keyword predicate — use in Where
decimal Prop.TextScore(string q) // full-text relevance score
decimal Prop.Similarity(string q) // semantic score on a [Searchable(_, Full)] text field
decimal Prop.Similarity(Vector q) // semantic score on a raw `Vector` field (bring your own vector)Description#
These primitives operate on a single entity's rows through an ordinary query — they behave exactly as any other query does.
They run in the query, and only there#
All three are database index operations: Matches and TextScore work against a full-text index built over the
field, and Similarity is a vector distance. None of them has an in-memory form, so they can be used only inside a
query the database executes — not on a row you have already loaded, not in a computed member, and not on the client.
Using one anywhere else is a compile error that says so.
Note.Where(n => n.Body.Matches(term)) // ✓ the database answers it, using the index
loadedNote.Body.Matches(term) // ✗ compile error — there is nothing to run it against hereIf you need the answer on a row you are holding, ask the query for it: filter or order by these in the query that loads the rows, and use what it returns.
Matches — the indexed keyword filter#
Prop.Matches(q) is a boolean full-text predicate: it's true when the field matches the keyword query q. It is
index-backed, so it's the efficient way to narrow a large table to the candidate rows before you rank them —
use it in Where.
TextScore — full-text relevance#
Prop.TextScore(q) scores how well the field matches the keyword query (higher = better). Unlike Matches, a bare
score is not index-backed, so ranking by it alone would scan the whole table — filter with Matches first, then
order the survivors by TextScore.
Similarity — semantic relevance#
Prop.Similarity(q) scores how close the field is to the query in meaning (higher = closer), so it finds
matches with no shared keywords. On a [Searchable(_, Full)] text field you pass a string and the engine
embeds it for you (once per query, never per row). On a raw Vector field you pass a vector you supply yourself.
Similarity needs a vector to compare against, so it's available only where one exists: a Full-mode searchable
field or a raw Vector field. On a [Searchable(Entity, TextOnly)] field (no vector) it's a compile error — use
Matches/TextScore there. Semantic ranking is active only when an embedding provider is configured; without one,
Similarity contributes nothing and your search degrades to full-text (see [Searchable]).
Runtime thresholds#
Because each primitive is a value, a relevance cutoff is just a comparison — the bound can be any expression (a
local, a parameter, a literal): Where(c => c.Bio.Similarity(q) > minScore).
Compose your own hybrid#
The platform hands you the pieces; you fuse them with plain arithmetic and choose the weights. The idiomatic hybrid
filters with the indexed Matches, then orders by a weighted blend of semantic and lexical relevance:
Candidate
.Where(c => c.Bio.Matches(q)) // indexed candidate set
.OrderByDescending(c => 0.7 * c.Bio.Similarity(q) + 0.3 * c.Bio.TextScore(q))
.Take(k)For a turnkey cross-entity search that fuses these for you over the shared corpus, use using Memory (semantic search) instead; reach for field search when you want entity-local results and control over the ranking.
Examples#
using Osyrin.Memory;
entity Article {
[Searchable] string Body;
}
List<Article> Search(string q) {
return Article
.Where(a => a.Body.Matches(q)) // indexed candidate filter
.OrderByDescending(a => a.Body.TextScore(q)) // rank the survivors
.ToList();
}using Osyrin.Memory;
entity Candidate {
[Searchable] string Bio;
}
List<Candidate> Best(string q, decimal minScore, int k) {
return Candidate
.Where(c => c.Bio.Similarity(q) > minScore) // threshold is any expression
.OrderByDescending(c => c.Bio.Similarity(q))
.Take(k)
.ToList();
}// Same `Candidate` as above — you decide how lexical and semantic scores are weighed.
List<Candidate> Hybrid(string q, int k) {
return Candidate
.Where(c => c.Bio.Matches(q))
.OrderByDescending(c => 0.7 * c.Bio.Similarity(q) + 0.3 * c.Bio.TextScore(q))
.Take(k)
.ToList();
}See also#
- [Searchable] — the
[Searchable]attribute that makes a field searchable (scope × mode) - using Memory (semantic search) — turnkey hybrid search over the shared corpus (
Memory-scope fields) - SearchHit — the result type
Memory.Searchreturns