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.
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.
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.
Related
If I try to filter the data by using containedIn as below I will be getting empty result because I'm trying to filter from pointer column type userProfile despite that I include the userProfile in the query.
However, I tried to use containedIn directly to another array column in User and seems it was working fine. is this kind of the below queries will not work? and what could be the alternative solutions?
const query = new Parse.Query('User');
query.equalTo('accountType', 'Student');
query.include('userProfile');
// search.subjects is an array
query.containedIn('userProfile.subjectsIds', search.subjects);
Unfortunately, you can't perform containedIn() on included objects. For containedIn() to work it has to be performed on a column of the class you are querying.
So in other words, you will have to perform this query directly on the userProfile class for containedIn() to work
I am currently making an app for punching in and out employees.
I have three tables, one for the Employes, one for the PunchIn and one for the PunchOut.
What I want is the following : when I create a PunchIn, it change the is_in entry (of the table Employees) to true and when I create a PunchOut, the is_in is false.
How can I deal with relations to make this?
Thanks!
Assuming the employee entity already exists, it's just:
punchInEntity.Employee.is_in = true;
and
punchOutEntity.Employee.is_in = false;
Be sure to save both entities afterward:
punchOutEntity.save();
punchOutEntity.Employee.save();
However, you might want to consider making is_in a computed attribute. In it's method, you could search for a punchOutCollection entity that is after the current date/time. Return false if one is found and true if not (as long as there is at least one punchInCollection entity). That way, you don't have to worry about updating is_in and it will always be correct.
Relations are not the best use case here. You need your employee entity to create a Punchin entity. So, why not directly update your employee entity instead of using relations ?
An example:
var emp = ds.Employees.find("ID = 1");
var myPunchin = new ds.Punchin({
employee: emp
});
myPunchin.save();
myPunchin.employee.is_in = true;
myPunchin.employee.save();
Even better without relations, you can replace myPunchin.employee by emp.
I have a firebase model where each object looks like this:
done: boolean
|
tags: array
|
text: string
Each object's tag array can contain any number of strings.
How do I obtain all objects with a matching tag? For example, find all objects where the tag contains "email".
Many of the more common search scenarios, such as searching by attribute (as your tag array would contain) will be baked into Firebase as the API continues to expand.
In the mean time, it's certainly possible to grow your own. One approach, based on your question, would be to simply "index" the list of tags with a list of records that match:
/tags/$tag/record_ids...
Then to search for records containing a given tag, you just do a quick query against the tags list:
new Firebase('URL/tags/'+tagName).once('value', function(snap) {
var listOfRecordIds = snap.val();
});
This is a pretty common NoSQL mantra--put more effort into the initial write to make reads easy later. It's also a common denormalization approach (and one most SQL database use internally, on a much more sophisticated level).
Also see the post Frank mentioned as that will help you expand into more advanced search topics.
In NetSuite, I have a scripted search of transactions that is expected to return results of several different transaction types. The results are then rendered in an nlobjList. I would like one of the columns of said list to be a link to the transaction that the list row represents.
In all NetSuite examples, this is accomplished something like:
var column = list.addColumn('number', 'text', 'Number', 'left');
column.setURL(nlapiResolveURL('RECORD','salesorder'));
column.addParamToURL('id','id', true);
Unfortunately, transaction is not an acceptable record type to pass to nlapiResolveURL, so I would need to dynamically detect the record type for each row. The setURL function does accept a second Boolean parameter that makes it dynamic per row, but I am not sure how this actually works. There are no examples, and the JSDocs do not explain its usage.
Does anyone have any guidance on generating a list with dynamic URLs in NetSuite?
If you set the dynamic argument to true, then the first argument should be a column listed in the data source that will contain the base URL.
column.setURL('base_url', true);
column.addParamToURL('id','id', true);
Then, on each record of your results, make sure you have a base_url that is set to the url you are looking for.
Note, the following example assumes a regular javascript object instead of the search result object.
rec.base_url = nlapiResolveURL('RECORD', rec.type)
Transaction field is just an abstraction for all transaction types. You can search them but can't load them.
The field you need to retrieve is recordtype. Sample code is below.
var recs = nlapiSearchRecord('transaction',null,null,new nlobjSearchColumn('recordtype'));
for(var i in recs)
url = nlapiResolveURL('RECORD',recs[i].getValue('recordtype'));
In the docs I see a lot of examples using index values as a part of the key name for a particular item --- but I don't understand how this is a consistent way to model your data.
For example let's say I have a list of articles:
https://gigablox.firebaseio.com/articles/
article1
article2
article3
When I'm ready to add article4 I know I can use:
var length = Object.keys($scope.articles).length;
And using AngularFire 0.5.0 I can save it with:
var name = 'article' + length + 1;
$scope.articles[name] = $scope.article;
$scope.articles.$save(name);
But what happens if I:
$scope.articles.$remove('article2');
And add another record using the same approach? We're likely to create duplicate key names.
To add a little complexity, let's add a single relationship and say that each article has comments.
What is the correct way to model this data in a Firebase collection?
Please use $add and let Firebase automatically generate chronologically ordered lists for you.
var ref = new Firebase("https://gigablox.firebaseio.com/articles/");
$scope.articles = $firebase(ref);
$scope.addArticle = function() {
$scope.articles.$add($scope.article);
}
$scope.removeArticle = function(id) {
$scope.articles.$remove(id);
}
Firebase automatically creates key names when you call $add. You can iterate over the key names using ng-repeat:
<div ng-repeat="(key, article) in articles">
<div ng-model="article"><a ng-click="removeArticle(key)">Remove</a></div>
</div>
EDIT: You should follow the suggestion from #Anant if you want an array-based collection.
However, for this specific scenario as outlined by #Dan Kanze, if you want to pull the key out of the URL (as would be done for a content management system, etc), you should generate your own keys unique to the content. For example, if you know that article names need to be unique, create a slug function that will:
Lowercase the article name
Replace spaces with underscores
etc..
If the article name changes, you would not delete the old entry. Instead, create a new entry in Firebase and use the old key to point to the new location for 301 redirects, etc.