1
votes

I'm trying to figure out with Breeze how to expand a specific navigation property for all items in an array of entities with a single request.

On this page of the Breeze documentation it shows the following way of achieving this:

var orderEntityType = selectedOrders[0].entityType;
var navProp = orderEntityType.getNavigationProperty("OrderDetails");
var navQuery = EntityQuery
    .fromEntityNavigation(selectedOrders, navProp)
    .expand("Product");
manager.executeQuery(navQuery).fail(handleFail);

However, when I tried this I get the error

The 'entity' parameter must be an entity

So I looked up in the documentation specifically for the EntityQuery.fromEntityNavigation method and it shows:

// 'employee' is a previously queried employee
var ordersNavProp = employee.entityType.getProperty("Orders");
var query = EntityQuery.fromEntityNavigation(employee, ordersNavProp); 

The documentation indicates that it is for a specific entity, not multiple. Which is consistent with the error I'm getting.

Is it possible to get all the navigation properties in a single request, or is the preferred way to iterate over an array making a request for each entity?

Basically, I'm working on filtering a list of items. My goal is that when a user selects a filter it then expands the needed navigation property at that time instead of loading all the data up front.

Thanks for the help.

2

2 Answers

2
votes

I think this might be a typo or some out of date information on the navigation properties documentation page. According to the API documentation for EntityQuery.fromEntityNavigation, the first parameter should be a single entity, not an array. Took a look at the breeze code, didn't see any evidence that an array of entities could be passed.

As a workaround, you could construct the query a bit differently. Continuing with the Order/OrderDetails scenario, you could do something like this:

var subsetOfOrders = ..., // array containing the subset of orders whose OrderDetails we need to load
    predicates = subsetOfOrders.map(function(order) { return new breeze.Predicate('OrderId', '==', order.OrderId()); }),
    predicate = breeze.Predicate.or(predicates),
    query = new breeze.EntityQuery('Orders').expand('OrderDetails').where(predicate);

manager.executeQuery(query)...

If you're able to query the order details directly you don't even need expand. Breeze will wire up the freshly loaded OrderDetails to the respective orders entities that are already cached in the entity manager:

var subsetOfOrders = ..., // array containing the subset of orders whose OrderDetails we need to load
    predicates = subsetOfOrders.map(function(order) { return new breeze.Predicate('OrderId', '==', order.OrderId()); }),
    predicate = breeze.Predicate.or(predicates),
    query = new breeze.EntityQuery('OrderDetails').where(predicate);

manager.executeQuery(query)...

This predicate based workaround may or may not be feasible depending on the number of orders you're dealing with. Could end up with a long query string. You could then consider using a dedicated controller action (ie "OrderDetailsByOrderId(int[] orderIds)" and use the withParameters EntityQuery method to load the order details using the new action.

2
votes

The documentation was in error. I just corrected it.

@Jeremy Danyow offered a superb explanation and a solution. I probably would use his approach to solve a specific use case.

The documentation now discusses the problem and describes yet another approach that might be more appropriate if you were trying to write a general utility.

// create an array of filter criteria (`wherePredicate`) for each order
var predicates = orders.map(function (order) {
    return EntityQuery.fromEntityNavigation(order,'OrderDetails')
                      .wherePredicate;
});

// OR the predicates together
var filter = breeze.Predicate.or(predicates);

EntityQuery.from('OrderDetails')
    .where(filter)
    .expand('Product')
    .using(em).execute().catch(handleFail);

Thanks to you both for identifying the problem and working through it.