How queries work
Querying is the tour: what a query is, filtering, sorting, paging, expand and the cache. This page is what sits underneath — the result of running one, the URL that goes over the wire, how a resource name is resolved, and the other ways to build a query.
The rest of this section covers:
- Query examples — a catalogue of common queries
- Where clauses — filtering, operators,
Predicate, any/all, and the object form, with the two shown side by side - Ordering, paging and expand —
orderBy,skip/take,inlineCount,expand,withParameters,noTrackingand query options - Projections —
select - Querying the cache —
executeQueryLocallyandFetchStrategy - Debugging queries
Executing a query
executeQuery returns a promise of a QueryResult:
| Property | Contents |
|---|---|
results | the top-level results: entities, or plain objects for a projection |
inlineCount | the total number of matches ignoring skip/take, if you asked for it — see Paging |
retrievedEntities | every entity the query returned, including those brought in by expand |
query | the query that ran |
entityManager | the manager that ran it |
httpResponse | the raw HTTP response |
A failed query rejects the promise:
try {
const { results } = await em.executeQuery(query);
showCustomers(results);
} catch (err) {
console.error('Query failed:', err.message);
}A query can also carry its manager, set with using, and run itself:
const { results } = await EntityQuery.from(Order).using(em).execute();execute() throws if the query has no manager.
Every EntityQuery method returns a new query rather than mutating the one it was called on, so a base query can be shared and specialised freely - see Querying for what that buys you.
What goes over the wire
The uriBuilder adapter turns a query into a URL. Breeze 3 ships one, UriBuilderJsonAdapter, and uses it by default (see Configuration). It serializes the query as Breeze JSON, translates property names to their server names with the naming convention, URL-encodes the JSON, and makes the result the query string.
The query above becomes a GET to breeze/NorthwindIBModel/Customers?%7B%22where%22.... Decoded, the query string is:
{"where":{"CompanyName":{"startswith":"B"}},"orderBy":["CompanyName"],"take":10}On a Breeze .NET server, the [BreezeQueryFilter] attribute reads that JSON and applies it to the IQueryable your controller method returns:
[HttpGet]
[BreezeQueryFilter]
public IQueryable<Customer> Customers() {
return PersistenceManager.Context.Customers;
}The attribute can also go on the controller class, which applies it to every method.
To see the JSON for a query without sending it, call query.toJSON(). It returns the same structure, but with client property names and the resource name included. See Debugging queries.
Sending the query in a POST body
A very long query can exceed URL length limits. usePost() sends the same JSON as the body of a POST instead:
const query = EntityQuery.from(Customer)
.where('companyName', 'startsWith', 'Alfreds')
.usePost();The server endpoint must accept POST and tell BreezeQueryFilter to read the body:
[HttpGet, HttpPost]
[BreezeQueryFilter(UsePost = true)]
public IQueryable<Customer> Customers() {
return PersistenceManager.Context.Customers;
}Resource names
Every query needs a target resource. These three are the same:
EntityQuery.from(Order);
new EntityQuery('Orders');
new EntityQuery().from('Orders');From resource name to URL
Breeze prefixes the resource name with the service name of the query's DataService. With a service name of breeze/NorthwindIBModel, 'Orders' becomes breeze/NorthwindIBModel/Orders. The manager normally supplies the DataService. Use query.using(dataService) to send a single query somewhere else.
If the resource name starts with http, Breeze uses it as the URL as is:
EntityQuery.from('https://api.example.com/breeze/Northwind/Orders');Breeze still appends the query JSON to that URL, joined with & if the URL already has a query string.
From resource name to EntityType
Breeze does not have to know which EntityType a remote query returns. The server might return Order entities from any of these, and Breeze recognises them when the results arrive:
EntityQuery.from(Order);
EntityQuery.from('OrdersAndDetails'); // a custom endpoint
EntityQuery.from('https://api.example.com/breeze/Northwind/Orders');It helps if Breeze knows the type in advance, because then it can check property names and convert values before sending. Consider a date comparison written as a string:
EntityQuery.from(Order).where('orderDate', '>=', 'January 1, 1998');Breeze knows 'Orders' returns Order. It looks up orderDate, sees that it is a DateTime, and parses the string:
{"where":{"OrderDate":{"ge":"1998-01-01T08:00:00.000Z"}}}(The string was parsed in the client's local time zone, UTC-8 here. Pass a Date if that matters to you.)
Target a resource Breeze does not recognise and it sends the string unchanged. The server will probably reject it:
{"where":{"OrderDate":{"ge":"January 1, 1998"}}}Tell Breeze the type with toType:
EntityQuery.from('OrdersAndDetails')
.where('orderDate', '>=', 'January 1, 1998')
.toType('Order');Default resource names
Breeze knew 'Orders' meant Order because of the metadata. Each entity type has a defaultResourceName:
const orderType = em.metadataStore.getAsEntityType('Order');
orderType.defaultResourceName; // "Orders"A Breeze .NET server sets it from the DbContext collection name, which is usually the plural of the type name and usually also the name of your controller method. If you write metadata by hand, you set it yourself.
The type name is not a resource name
'Order' (the type) is not 'Orders' (the resource). This query sends the date string unconverted, exactly as the 'OrdersAndDetails' query did:
EntityQuery.from('Order').where('orderDate', '>=', 'January 1, 1998');For a query against the cache the distinction is stricter. There is no endpoint there, so the resource name must resolve to an entity type, or the query throws:
Cannot find an entityType for resourceName: 'Order'. Add 'EntityQuery.toType()' to your
query, or call 'MetadataStore.setEntityTypeForResourceName()' to register an EntityType
for this resourceName.Registering more resource names
To use other names without adding toType each time, register them with the MetadataStore. The resource name comes first:
const store = em.metadataStore;
store.setEntityTypeForResourceName('Order', 'Order');
store.setEntityTypeForResourceName('OrdersAndDetails', 'Order');Now EntityQuery.from('Order') works remotely and against the cache.
getEntityTypeNameForResourceName goes the other way. It returns the namespace-qualified type name, or undefined if the name is not registered:
store.getEntityTypeNameForResourceName('Orders'); // "Order:#<your model namespace>"There is no public API that lists every registered resource name.
Other ways to build a query
EntityQuery has static factories for common cases. They are covered with examples in Query examples:
| Method | Builds a query for |
|---|---|
EntityQuery.fromEntityKey(key) | the entity with that EntityKey |
EntityQuery.fromEntities(entities) | fresh copies of entities you already have (all the same type) |
EntityQuery.fromEntityNavigation(entity, navProp) | the entities at the end of a navigation property |
See the EntityQuery API reference for the full surface.