Error loading entities with a navigation property using custom metadata - breeze

I have the following implementation of a custom metadata scenario using the boiler plate with breeze.metadata-helper 1.0.7 and BreezeJS 1.4.16:
function addFoo() {
addType({
shortName: 'Foo',
dataProperties: {
Id: { type: ID },
BarId: { type: ID }
},
navigationProperties: {
Bar: {
entityTypeName: 'Bar',
associationName: 'Foo_Bar',
foreignKeyNames: ['BarId']
}
}
});
}
function addBar() {
addType({
shortName: 'Bar',
dataProperties: {
Id: { type: ID },
}
});
}
Upon completion of query (provided below) execution i receive the following error: TypeError: newValue.getProperty is not a function.
var query = breeze.EntityQuery.from('Foos').expand('Bar');
manager.execute(query).catch(function(error) { });
HTTP response contains the following JSON:
[{"Id":1,"BarId":2,"Bar":{"Id":2}}]
Though this query works fine if i replace foreignKeyNames: ['BarId'] with invForeignKeyNames: ['Id']. And still, in the latter case the navigation property Bar() of foo entity is not of entity type Bar but a simple javascript object.
The questions are:
1) Why the query with foreignKeyNames fails
2) Why foo.Bar() is not an entity of type Bar
Thanks.

I think the issue is with how you are serializing the result of your query. Take a look at the JsonResultsAdapter documentation where it discusses how to handle references in the json payload. You may need a custom JsonResultsAdapter ( pretty easy to write once you understand the idea).

Related

Loopback POST array of entry?

I want to insert 10 entries with one query against 10 queries.
I read that it's possible to do it by sending an array like this :
But I get this error:
Do I need to set something? I don't know what to do at all.
Repo with a sample : https://github.com/mathias22osterhagen22/loopback-array-post-sample
Edit:
people-model.ts:
import {Entity, model, property} from '#loopback/repository';
#model()
export class People extends Entity {
#property({
type: 'number',
id: true,
generated: true,
})
id?: number;
#property({
type: 'string',
required: true,
})
name: string;
constructor(data?: Partial<People>) {
super(data);
}
}
export interface PeopleRelations {
// describe navigational properties here
}
export type PeopleWithRelations = People & PeopleRelations;
The problem with your code was :
"name": "ValidationError", "message": "The People instance is not
valid. Details: 0 is not defined in the model (value: undefined);
1 is not defined in the model (value: undefined); name can't be
blank (value: undefined).",
Here in above as in your #requestBody schema, you are applying to insert a single object property, where as in your body are sending the array of [people] object.
As you can see in your people.model.ts you have declared property name to be required, so system finds for the property "name", which obviously not available in the given array of object as primary node.
As you are passing index array, so its obvious error that you don't have any property named 0 or 1, so it throws error.
The below is the code hat you should apply to get insert the multiple, items of the type.
#post('/peoples', {
responses: {
'200': {
description: 'People model instance',
content: {
'application/json': {
schema: getModelSchemaRef(People)
}
},
},
},
})
async create(
#requestBody({
content: {
'application/json': {
schema: {
type: 'array',
items: getModelSchemaRef(People, {
title: 'NewPeople',
exclude: ['id'],
}),
}
},
},
})
people: [Omit<People, 'id'>]
): Promise<{}> {
people.forEach(item => this.peopleRepository.create(item))
return people;
}
You can also use this below
Promise<People[]> {
return await this.peopleRepository.createAll(people)
}
You can pass the array of your people model by modifying the request body.If you need more help you can leave comment.
I think you have a clear solution now. "Happy Loopbacking :)"

how to implement mutation responses on a local falcor Model dataset

Given that I have an example Model:
var model = new falcor.Model({
cache: {
userById: {
"1": {
name: "User",
email: "user#email.com"
}
},
users: {
current: null
}
}
});
This is a local model that I'm using for testing purposes, and I would like to implement it on a call to users.login so the user so that I can call:
model.call(['users', 'login'], ['user', 'password'])
I realized that if I do this:
var model = new falcor.Model({
cache: {
userById: {
"1": {
name: "User",
email: "user#email.com"
}
},
users: {
current: null,
login: function(user, password) {
console.log('this code is reached', user, password);
// what to return in order to mutate model?
}
},
}
});
When I do the call it gets there, but I can't figure out how to mutate the model as part of the response; on the server side we return the paths with values and invalidates, and it just works, but here I tried:
// trying returning as a jsonGraph response, don't work
login: function() {
return {
jsonGraph: {
users: {
current: {$type: "ref", value: ['userById', '1']}
}
},
paths: [['users', 'current']]
}
}
// trying returning as a path set mutation list, don't work
login: function() {
return [{path: ['users', 'current'], value: {$type: "ref", value: ['userById', '1']}}]
}
// trying force call to set on the model, don't work
login: function() {
this.set([
{path: ['users', 'current'], value: {$type: "ref", value: ['userById', '1']}}
])
}
// trying using ModelResponse, got an example on some external sources, don't work
login: funtion() {
return new ModelResponse((observer) => {
observer.onNext({
jsonGraph: {
users: {
current: {$type: "ref", value: ['userById', '1']}
}
},
paths: [['users', 'current']]
});
observer.onCompleted();
});
}
Now I don't know what else to try; I need a simple way to declare mutations after a call into a local model, if you know how to solve this, please let me know here.
Thanks.
The client model cache only supports JSONGraph, which b/c it is essentially just JSON with some conventions, doesn't support functions. So, when working with a falcor model cache and no dataSource/middle tier router, it is not possible to implement calls.
This can be kind of annoying when prototyping/testing, as a router is conceptually more difficult than a simple JSON cache object. I ran into this a while ago, so I wrote a dataSource module to support it: falcor-local-datasource. The dataSource is initialized with a graph object that does support function nodes, and as with your above examples, will mutate the graph based on the function's returned JSONGraphEnvelope or an array of PathValues.

Breeze URL generation for REST API and navigation properties

Ive been trying to use breeze with third party RESTful API - the API expects parameters of two types - deep linking like - localhost/request/5 for single entities (ie. request with id = 5) and parametrised queries using JSON encoded in URL (transformed by breeze with no problem).
The main problem is to make breeze to create request with URL such as:
localhost/request/{id}
not JSON encoded GET, while using breeze query "withParameters( {workorderid: id})"
And the second part of problem is using syntax like:
var query = breeze.EntityQuery
.from('request')
.withParameters(parameters)
.expand('requestDetails');
To query for two entities - main - request - and secondary - requestDetails (with deffered on access querying for value of the secondary object).
The result should be like on this image:
http://postimg.org/image/prurk75ol/
My model is defined by hand with two entities:
metadataStore.addEntityType({
shortName: "request",
namespace: "servicedesk",
dataProperties: {
workorderid: {
dataType: DT.Identity,
isPartOfKey: true
},
ignorerequest: {
dataType: DT.Boolean
}
},
navigationProperties: {
requestDetails: {
entityTypeName: "requestDetails:#servicedesk",
isScalar: true,
associationName: "request_requestDetails",
foreignKeyNames: ["workorderid"]
}
}
});
metadataStore.addEntityType({
shortName: "requestDetails",
namespace: "servicedesk",
dataProperties: {
workorderid: {
dataType: DT.Identity,
isPartOfKey: true
},
group: {
dataType: DT.String
},
description: {
dataType: DT.String
}
},
navigationProperties: {
request: {
entityTypeName: "request:#servicedesk",
isScalar: true,
associationName: "requestDetails_request",
foreignKeyNames: ["workorderid"]
}
}
Ive found example of this: https://github.com/Breeze/breeze.js.labs/blob/master/breeze.ajaxrestinterceptor.js , it looks like i can change url generation by intercepting ajax calls, can this be done for angular.breeze adapter?
I don't fully understand your question. What query are you having trouble with? Please edit your answer so we can help.
BUT I do see what looks like a metadata problem with your definition of the request type's requestDetails navigation property. Both the property name and the semantics suggest this should return a collection but you've defined it as a scalar.
requestDetails: {
entityTypeName: "requestDetails:#servicedesk",
isScalar: true, // <-- HUH?
associationName: "request_requestDetails",
foreignKeyNames: ["workorderid"]
}
I think you want isScalar: false,

Breeze How to write entity related queries against local cache

I am trying to figure out how to write a query that will filter my related entities using the executeQueryLocally.
Here is my example:
var queryStageConfigs = breeze.EntityQuery
.from("Stages")
.where("StageConfig.ConfigValue", '==', 'Green')
.expand("StageConfig");
var stageConfigs = self.manager.executeQueryLocally(queryStageConfigs);
This the error I am getting back:
Error: unable to locate property: ConfigValue on entityType: Stages:#
MetadataStore
//Stages Entity
metadataStore.addEntityType({
shortName: "Stages",
//namespace: "MonitoringCenter",
dataProperties: {
id: { dataType: DT.Int64, isPartOfKey: true },
institutionId: { dataType: DT.Int64 },
qualifiedName: { dataType: DT.String },
displayName: { dataType: DT.String },
displayOrder: { dataType: DT.Int64 },
},
navigationProperties: {
institution: {
entityTypeName: "Stages",
isScalar: true,
associationName: "Institution_Stages",
foreignKeyNames: ["institutionId"]
},
stageConfig: {
entityTypeName: "StageConfig",
isScalar: false,
associationName: "Stages_StageConfig"
}
}
});
metadataStore.setEntityTypeForResourceName("Stages", "Stages");
//StageConfig Entity
metadataStore.addEntityType({
shortName: "StageConfig",
//namespace: "MonitoringCenter",
dataProperties: {
id: { dataType: DT.Int64, isPartOfKey: true },
stageId: { dataType: DT.Int64 },
configName: { dataType: DT.String },
configValue: { dataType: DT.String },
},
navigationProperties: {
stages: {
entityTypeName: "StageConfig",
isScalar: true,
associationName: "Stages_StageConfig",
foreignKeyNames: ["stageId"]
}
}
});
metadataStore.setEntityTypeForResourceName("StageConfig", "StageConfig");
I am hand writing the JsonResultsAdpater to get the JSON data into the entities that have created using the metadataStore setting up the relationship between the entities.
When I query the Stages entities I can see the StageConfigs array and it is not empty.
Any clue on what I may be doing wrong?
Any help would be greatly appreciated!
Ok, your example is a little confusing because I can't tell the cardinality of the relationships based on your names. In general, I think of a pluralized name like 'stages' as being a collection of 'Stage' entities (i.e. isScalar = false). But in your case the 'stages' property is actually scalar and its inverse, the 'stageConfig' property, a singularized name, is actually non-scalar. If this assumption is correct then the reason that your query will not work is because the stage.stageConfig property needs to be a scalar in order for your query to work, unless you want to use the 'any/all' operators. i.e. something like
var queryStageConfigs = breeze.EntityQuery
.from("Stages")
.where("StageConfig", "any", "ConfigValue", "==", 'Green');
.expand("StageConfig");
( and shouldn't the query be called 'queryStages' because you are querying and returning 'stages')
Also, just a nit, but its probably a good idea to keep all of your entityType names singular and your resource names plural. This is not a requirement, but it is certainly confusing the way you have one entityType singularized ( StageConfig) and another pluralized (Stages).
I don't see any mention in your code about using the camelCase naming convention, but the example seems to indicate that you are assuming that this is set.

ExtJS4.1: Why model.save() sends wrong thing to server?

I tried to use model.save() to POST a new user. But I check request payload and found that it not only sent the data, but also sent other parts of the model. That makes my server cannot parse the payload.
The request payload generated :
{"phantom":true,"internalId":"ext-record-58","raw":{},"data":{"userId":0,"userName":"Amy"},"modified":{"userName":""},"hasListeners":{},"events":{},"stores":[],"dirty":true,"id":"AM.model.User-ext-record-58"}
But the desired request payload should be :
{"userId":0,"userName":"Amy"}
And I am aware that the "phantom" of my model is false before I call model.save(). But it becomes true in the request payload. Is it a clue?
Model:
Ext.define('AM.model.User',{
extend: 'Ext.data.Model',
fields: [
{ name: 'userId', type: 'int' },
{ name: 'userName', type: 'string' },
{ name: 'createdTime', type: 'string' },
],
idProperty: 'userId',
associations: [
{
type: 'hasOne',
model: 'AM.model.ModelA',
name:'modelA',
associationKey:'modelA',
getterName:'modelA'
},
{
type: 'hasOne',
model: 'AM.model.ModelB',
name:'modelB',
associationKey:'modelB',
getterName:'modelB'
}
],
proxy: {
type: 'rest',
success:true,
url:'../restful/users',
writer:{
type:'json',
getRecordData:function(record){ //parse createdTime to the format Y-m-d
record.set('createdTime', Ext.Date.format(new Date(record.get('createdTime')), "Y-m-d"));
return record;
}
},
reader: {
type: 'json'
}
}
});
This is the view which has the data to be posted. The view will fill the data to the model:
Ext.define('AM.view.UserRegisterForm',{
extend:'Ext.form.Panel.',
alias:'widget.userRegisterForm',
fields:new Array(), //I want to render the fields in xtemplate, so instead of adding the fields to items, I use an array to manage them.
retrieveData(model){
model.set('userName', this.fields[0].getValue());
model.set('createdTime',this.fields[1].getValue());
}
}
The function in the controller, which sends the POST request:
postUser:function(){
var userRegisterForm= this.getUserRegisterForm();
var userModel = this.getUserModel();
var user= new userModel();
var me = this;
userRegisterForm.retrieveFieldData(user);
console.log(user); //the data in console looks fine!
user.save({
success: function(response) {
//do something...
},failure:function(response) {
alert('fail');
}
});
}
You are returning the full record when you override getRecordData Where as you are just meant to return the records data. record.getData()
Some extra advice. Don't override getRecordData to set the models creation date. Use the models defaultValue property to give assign it a new Date if one doesn't exist.

Resources