Querying
The query builder
Section titled “The query builder”IContactStore.Query() returns a ContactQuery — a small fluent builder. Nothing runs until you
call a terminal method (ToListAsync, FirstOrDefaultAsync, CountAsync), and the native read is
performed off the calling thread, so it is safe to await straight from the UI thread.
var results = await contactStore .Query() .Where(ContactField.FamilyName, "Smith", ContactFilterOperation.StartsWith) .OrderBy(ContactSortField.FamilyName) .ThenBy(ContactSortField.GivenName) .Take(50) .ToListAsync(ct);Field filters are pushed down to the native query where the platform supports it — ContentProvider
selections on Android, a CNContact fetch predicate on iOS — and are always re-applied in-memory,
so the result is exact no matter how much the platform could translate.
Filtering
Section titled “Filtering”By field
Section titled “By field”// default operation is Containsvar johns = await contactStore.Query() .Where(ContactField.GivenName, "John") .ToListAsync();
// by phone number — matches if ANY of the contact's numbers matchvar byPhone = await contactStore.Query() .Where(ContactField.Phone, "555") .ToListAsync();
// by emailvar byEmail = await contactStore.Query() .Where(ContactField.Email, "@example.com") .ToListAsync();Multiple field filters are ANDed by default:
var results = await contactStore.Query() .Where(ContactField.GivenName, "J", ContactFilterOperation.StartsWith) .Where(ContactField.FamilyName, "Smith") .ToListAsync();Call Match(ContactFilterMatch.Any) to OR them instead.
Search box
Section titled “Search box”Search(text) is the “one text box over everything” filter — it matches given name, family name,
display name, phone numbers and email addresses, and switches the query to Match.Any:
var results = await contactStore.Query() .Search(searchText) .OrderBy(ContactSortField.FamilyName) .ToListAsync();Anything else
Section titled “Anything else”Pass a plain predicate for anything the fields don’t cover. It runs in-memory after the fetch, and multiple predicates are ANDed:
var birthdays = await contactStore.Query() .Where(c => c.Dates.Any(d => d.Type == ContactDateType.Birthday)) .ToListAsync();Sorting and paging
Section titled “Sorting and paging”var page = await contactStore.Query() .Where(ContactField.FamilyName, "A", ContactFilterOperation.StartsWith) .OrderBy(ContactSortField.FamilyName) .ThenBy(ContactSortField.GivenName) .Skip(10) .Take(20) .ToListAsync();OrderBy replaces any previous sort; ThenBy adds a secondary one and throws if no OrderBy
precedes it. Both take a descending flag. Sorting and paging are applied after the fetch.
Terminal methods
Section titled “Terminal methods”| Method | Returns |
|---|---|
ToListAsync(ct) |
IReadOnlyList<Contact> |
FirstOrDefaultAsync(ct) |
Contact? |
CountAsync(ct) |
int |
Fields and operations
Section titled “Fields and operations”ContactField: GivenName, FamilyName, MiddleName, NamePrefix, NameSuffix, Nickname,
DisplayName, Note, Company, JobTitle, Department, Phone, Email.
ContactFilterOperation: Contains (default), StartsWith, EndsWith, Equals. All comparisons
are case-insensitive.
What gets translated natively
Section titled “What gets translated natively”| Field | Android | iOS |
|---|---|---|
Name fields (GivenName, FamilyName, MiddleName, NamePrefix, NameSuffix, DisplayName, Nickname) |
✅ selection query | ✅ for StartsWith/Equals on given/family/display name |
Phone, Email |
✅ selection query | ❌ |
Note, Company, JobTitle, Department |
❌ | ❌ |
Predicate Where(Func<Contact,bool>) |
❌ | ❌ |
❌ means the platform reads the full contact list and the builder filters it in-memory — correct, just
more work. CNContact’s only search predicate matches on a name-token prefix, which is why iOS
pushes down StartsWith/Equals but not Contains/EndsWith, and never in Match.Any mode.
Extension methods
Section titled “Extension methods”Get Family Name First Letters
Section titled “Get Family Name First Letters”Returns a sorted, distinct list of uppercase first letters from all contacts’ family names:
var letters = await contactStore.GetFamilyNameFirstLetters();// ['A', 'B', 'C', 'D', ...]This is useful for building alphabetical index/jump lists in contact list UIs.


