# Contains, StartsWith, EndsWith

> Tests whether a string contains, begins with, or ends with another string. The match is case-sensitive and literal — like C#'s String.Contains, the argument is a plain string, not a pattern. The argument may be a computed value. It gives the same answer whether it runs in the browser, in a function body on the server, or pushed down into a database query.

<!-- id: function-string-search · area: function · stability: stable · html: https://osysharp.com/reference/function/string-search/ -->

## Summary        {#summary}
`s.Contains(x)`, `s.StartsWith(x)` and `s.EndsWith(x)` test a string against another string. They behave exactly like
C#'s `String.Contains`/`StartsWith`/`EndsWith`: an ordinal, **literal** match. They work on a local string and inside a
query predicate, and they mean the same thing in both.

## Signature      {#signature}
```osy syntax
<string>.Contains(<string>)   -> bool
<string>.StartsWith(<string>) -> bool
<string>.EndsWith(<string>)   -> bool
```

## Description    {#description}

### The match is literal, not a pattern   {#literal}

The argument is a plain string. A `%` or `_` in it is an ordinary character — `total.Contains("50%")` is true only of a
string that actually contains "50%". This mirrors C# (and EF Core, which escapes these characters when it translates
the call to SQL) — there is no wildcard here.

For a WILDCARD search — `%` for any run of characters, `_` for exactly one — use [Text.Like](https://osysharp.com/reference/function/text-like/), which is a
separate name precisely so the two cannot be confused. It pushes down into a query, and an anchored pattern
(`"RUSH-%"`) can use an index.

For full regular expressions on a string you already hold (a validator, a UI action), use [Regex](https://osysharp.com/reference/stdlib/regex/). Note it
runs **in memory** only: a regex inside a query `.Where(...)` predicate does not compile, because it has no SQL form.
Inside a query the searches that push down are these three literal tests, `Text.Like`, and — for a `[Searchable]`
field — full-text `.Matches(...)`.

### The match is case-sensitive   {#case}

`"ACME Ltd".Contains("acme")` is **false**. If you want a case-insensitive search, lower-case both sides:

```osy title="the match is case-sensitive — lower both sides" syntax
c.Name.ToLower().Contains("acme")
```

### The argument may be computed   {#computed-argument}

It does not have to be a literal written in the source — a variable, a parameter, or any expression that yields a
string works, in every context:

```osy title="the needle may be computed, not just a literal" syntax
name.StartsWith(prefix)   // prefix is a parameter, not a literal
```

### One answer, wherever it runs   {#same-everywhere}

The same expression gives the same result whether it is evaluated on a local string in a browser action, in a function
body on the server, or compiled into SQL and run by the database. That is not a coincidence — it is the property the
three implementations are tested against each other to hold.

## Examples       {#examples}
```osy title="filter orders by a code prefix" test app=string-search
entity Order { string Code; }

List<Order> RushOrders() {
  return Order.Where(o => o.Code.StartsWith("RUSH-")).ToList();
}

bool MatchesTerm(string note, string term) {
  return note.ToLower().Contains(term.ToLower());   // computed argument; lower BOTH sides to ignore case
}
```

## See also       {#see-also}
- [Text.Like](https://osysharp.com/reference/function/text-like/) — the WILDCARD search (`%`, `_`), which does push down into a query
- [Regex](https://osysharp.com/reference/stdlib/regex/) — for full regular expressions on an in-hand string (in memory; not usable in a query predicate)
- [Text.Split](https://osysharp.com/reference/function/text-split/) — the other string builtins
- [execution side](https://osysharp.com/reference/function/execution-side/) — why this runs in the browser too
