Every Breeze query needs a target resource that identifies the destination of the query.
You typically aim a Breeze query at a particular resource with a .from(resource)
clause like this:
var query = breeze.EntityQuery.from('Orders');
This topic covers how Breeze
EntityType
of the query … if it has one“Orders” in that example is a resource name. When you execute the query targeting a remote service as we do here …
manager.execute(query);
Breeze turns that name into a URI by prefixing it with the dataService.serviceName
for this query. If the serviceName
is “breeze/Northwind/”, Breeze would compose this relative URL:
breeze/Northwind/Orders
The
EntityManager
that executes the query usually supplies theDataService
but you can specify a different one for a particular query with a.using
clause.
If your web page base address is “http://localhost:58066/”, the absolute URL to which the query will be sent is likely to be this:
http://localhost:58066/breeze/Northwind/Orders
Alternatively you can specify the absolute URI as the resource name and breeze will use that exact URL instead. The following .from
clause results in the same URL:
var query = breeze.EntityQuery
.from('http://localhost:58066/breeze/Northwind/Orders');
The absolute URI option allows you to compose the entire URL (including query string), without Breeze’s assistance (or interference).
Note that, while the absolute URI may be the same as the one breeze would compose, the resource name is different. The resource name is the string in the .from
clause. The following two resource names are not the same, even if they produce the same query result.
1) 'Orders'
2) 'http://localhost:58066/breeze/Northwind/Orders'
To understand why, we must see how Breeze relates a resource name to an EntityType
.
Breeze does not have to know the root EntityType
targeted in a remote query. For example, the server might return Order
entity JSON in response to all of these queries:
.from('Orders') // plural
.from('http://localhost:58066/breeze/Northwind/Orders')
.from('Order') // singular
.from('OrderAndDetails') // returns Order and their OrderDetails
.from('Foos') // who knew?
Breeze will wait for the query results to arrive and let the prevailing JsonResultsAdapter
translate the JSON payload into Order
entities.
Perhaps some or all of the JSON objects aren’t entities at all. That’s OK too. Such data objects are returned to the caller in the query response “as is”.
Although Breeze doesn’t have to know the target type, it can be helpful … or even necessary for certain queries. For example, consider a query that filters by date.
breeze.EntityQuery.from('Orders')
.where('OrderDate', '>=', 'January 1, 1998')
The comparison value, ‘January 1, 1998’, is a string. What is breeze supposed to do with that?
If Breeze can determine that the root type for ‘Orders’ is Order
, it can discover that ‘OrderDate’ is a DateTime
type and convert the string into a suitable date format for the URL like this:
~/Orders?$filter=OrderDate ge datetime'1998-01-01T00:00:00.000Z'
Suppose we query again with a legitimate but different resource name that breeze doesn’t know about:
// return Orders and their related OrderDetails
breeze.EntityQuery.from('OrdersAndDetails')
.where('OrderDate', '>=', 'January 1, 1998')
Breeze doesn’t know that the ‘OrderDate’ property is a DateTime
type and simply uses ‘January 1, 1998’ as is. The URL becomes.
~/OrdersAndDetails?$filter=OrderDate ge 'January 1, 1998'
The server is expecting the comparison value to be a date. It could choose to reject the query rather than try to parse the string. The response might be:
"The query specified in the URI is not valid.
A binary operator with incompatible types was detected.
Found operand types 'Edm.DateTime' and 'Edm.String' for
operator kind 'GreaterThanOrEqual'."
This is the response from a Web API / EF server.
The easy solution is to tell Breeze what type to expect with the toType
clause as follows:
breeze.EntityQuery.from('OrdersAndDetails')
.where('OrderDate', '>=', 'January 1, 1998')
.toType('Order')
Now Breeze can determine that the ‘OrderDate’ property is a DateTime
type and parse the string date. It composes the proper query URL and the server happily responds with orders and their details.
Why didn’t we need .toType
for the first query that targets ‘Orders’?
.from('Orders')
Why did Breeze know that an ‘Orders’ query concerns entities of type Order
?
The answer lies in the metadata. Let’s look at a part of the metadata for Order
.
var orderType = manager.metadataStore.getEntityType('Order');
orderType.defaultResourceName; // "Orders"
In this example, the defaultResourceName
returns “Orders” … which just happens to be the resource name we used in our query. That’s all Breeze needed to correlate the query resource name with the Order
type.
Who set defaultResourceName
to “Orders”? That depends upon how you got your metadata.
You might have done it yourself, if you coded your metadata by hand. If you get your metadata from the server, something there must have set it.
For example, when the metadata are generated from an Entity Framework model, the
defaultResourceName
is set to theDbContext
collection name. The collection name is typically the plural of the entity type name. Accordingly, “Orders” is the collection name of theOrder
type.Your server’s GET endpoint is also probably called “Orders” because it is conventional for the endpoint name to be the plural of the type name.
These conventions jointly conspire to yield the happy result that “Orders” is the breeze
defaultResourceName
and the endpoint name and the EF collection name. It all just works.
Don’t confuse the EntityType
name with a resource name. The resource name is just a string, one that should identify an endpoint.
There can be only one defaultResourceName
and it most likely is not the same as the EntityType
name. It isn’t the same in our example: “Orders” (the default resource) is not “Order” (the type name).
Returning to our example, the following query must fail in the same way that it failed when we targeted “OrdersAndDetails”:
breeze.EntityQuery.from('Order') // the EntityType name
.where('OrderDate', '>=', 'January 1, 1998');
You can cure the problem with .toType
as before.
breeze.EntityQuery.from('Order') // the EntityType name
.where('OrderDate', '>=', 'January 1, 1998')
.toType('Order');
It’s easy in Breeze to take the remote service query object and apply it to the local cache.
var query = breeze.EntityQuery.from('Orders') // back to 'Orders'
.where('OrderDate', '>=', 'January 1, 1998');
You can execute it synchronously …
var orders = manager.executeQueryLocally(query);
or asynchronously
var orders;
var qLocal = query.using(breeze.FetchStrategy.FromLocalCache);
manager.executeQuery(qLocal)
.then(function(data) { orders = data.results; });
The resource name of a local cache query must resolve to an EntityType
. Breeze isn’t going to produce an HTTP URL that targets a remote endpoint. It’s going to look for entities in cache where there are no endpoints. The cache is organized by EntityType
. To resolve a cache query, Breeze must translate the query resource name to an EntityType
in metadata.
The example query above works just fine because Breeze can translate from the “Orders” resource name to the Order
type via the defaultResourceName
.
Some folks want to use the EntityType
name when targeting the local cache. They want to write .from('Order')
rather than .from('Orders')
and they don’t want to tack on the .toType
clause. They often forget to add .toType
and wonder why the query fails.
The error message explains the problem and suggests a resolution.
Cannot find an entityType for resourceName: 'Order'.
Consider adding an 'EntityQuery.toType' call to your query or calling
the MetadataStore.setEntityTypeForResourceName method to register
an entityType for this resourceName.
Evidently, we can specify additional mappings between a resource name and an EntityType
with the MetadataStore.setEntityTypeForResourceName
method. Let’s do that for the “Order” type name:
var meta = manager.metadataStore;
meta.setEntityTypeForResourceName('Order', 'Order');
Now this query works both remotely and locally without assistance from .toType
:
var query = breeze.EntityQuery.from('Order') // use the type name
.where('OrderDate', '>=', 'January 1, 1998')
.using(manager); // pin the query to this manager
query.execute.then(...); // remote async
query.using(breeze.FetchStrategy.FromLocalCache)
.execute.then(...) // local async
var orders = query.executeLocally(); // local synchronous
Don’t stop there. Feel free to register additional resource names:
meta.setEntityTypeForResourceName('OrderAndDetails', 'Order');
meta.setEntityTypeForResourceName('Foos', 'Order');
Notice that the resource name is the first parameter, the EntityType
name is the second parameter. Don’t let the setEntityTypeForResourceName
method name fool you.
MetadataStore.getEntityTypeNameForResourceName
can tell you the EntityType
for a resource name.
After adding resource names as we did above, all of the following return the Order
type:
var meta = manager.metadataStore;
meta.getEntityTypeForResourceName('Orders');
meta.getEntityTypeForResourceName('Order');
meta.getEntityTypeForResourceName('OrderAndDetails');
meta.getEntityTypeForResourceName('Foos');
There is no public API for listing the registered resource names. We’ll bet you can find the private mappings list in the
MetadataStore
object itself. Be careful. Non-public APIs can change without warning in future versions of Breeze.