SAPUI5 oData model v2 submitChanges. Not always able to retrieve created data. Why? - odata

I'm creating oData using the createEntry() method of the oData model (v2),
Then, on submitChanges(), I'm retrievinvg the newly created data with this.getPageBlocks() below.
However, intermittently, this.getPageBlocks() does not return the newly created entry, meaning the data is not refreshed correctly for the end-user.
Why is that? At what point does submitChanges() execute? I'm guessing it's executing before the actual backend data has been created, which would explain why it's not there when I run this.getPageBlocks()
pageModel.createEntry("/PageBlocks", {
...
...
}
then....
pageModel.submitChanges({
success: function (oData) {
this.getPageBlocks();
}

Actually, after submitChanges your entry should get updated. If you want to get the newly created entry, you should do the following:
_createEntry() {
const oModel = this.getView().getModel();
this._oEntry = oModel.createEntry("/PageBlocks", {...})
}
onSubmitButtonPress() {
const oModel = this.getView().getModel();
oModel.submitChanges({
success() {
this._oEntry.getObject() // Will return the newly created entry
}
})
}

Related

Using before or after save in parse cloud code to add a userId to another table

I am making a running app and would like to add the username and userId to another Class along with the User Class as soon as they register. I am having difficulty with the userId not saving to the Runner Class, the username saves just fine. Is this because I am using the code in the beforeSave function? How would I go about fixing this?
Parse.Cloud.beforeSave(Parse.User, function(request, response) {
// Check if the User is newly created
if (request.object.isNew()) {
// Set default values
var RunnerClass = Parse.Object.extend("Runner");
var runner = new RunnerClass;
runner.set("username", request.object.get("username"));
runner.set("userId", request.object.id);
runner.save(null,{
success:function(runner) {
response.success(runner);
},
error:function(error) {
response.error(error);
}
});
}
response.success();
});
If relevant this is for iOS.
I attempted to user the afterSave but nothing gets added into the Runner class only the User class
I used the following code :
Parse.Cloud.afterSave(Parse.User, function(request, response) {
// Check if the User is newly created
if (!request.object.existed()) {
// Set default values
var RunnerClass = Parse.Object.extend("Runner");
var runner = new RunnerClass;
runner.set("username", request.object.get("username"));
runner.set("userId", request.object.id);
runner.save()
}
});
In the beforeSave handler a new object will not have an id yet, so the userId of the RunnerClass will not have a value.
You can use the afterSave handler and create the RunnerClass there. In an afterSave handler, you can use request.object.existed() instead of request.object.isNew(). Immediately after the first time an object is saved, existed() will return false to indicate that the last server operation created the object. If the object is subsequently saved again, the object will have already existed on the server, so existed() will return true from then on.
UPDATE
I have tested your afterSave function on my own Parse Service and it works well. It creates a Runner class with correct userId and username. This is the exact code that I used
Parse.Cloud.afterSave("_User", function(request, response) {
if (!request.object.existed()) {
// Set default values
console.log("User did not yet exist")
var RunnerClass = Parse.Object.extend("Runner");
var runner = new RunnerClass;
runner.set("username", request.object.get("username"));
runner.set("userId", request.object.id);
runner.save()
} else {
console.log("User exists")
}
})

Added Entitties don't change entityState after entityManager.saveChanges()

I create a new Entity in EntityManager like this:
var newCust = manager.createEntity('Customer', { Name: 'MyNewCustomer' });
it's Id is correctly generated. On a Button-Click I save the Entity on the Server:
manager.saveChanges([newCust]).then(function (saveResult) {
alert('saved!');
}
Now everything works perfect BUT, the Entity in newCust keeps it's EntityState "Added". It doesn't change to "Unchanged".
I've debugged a little and found out, that in the function "mergeEntity(mc, node, meta)" of breeze, where the following code happens - no entity is found (eventhough the key it searches is the same Id - the Id-Fixup worked correctly):
var targetEntity = em.findEntityByKey(entityKey);
So everytime I save my new Entity, a new Entity will be created in my EntityManager. When I reload the page, the entity is flagged correctly and works. The only problem is, that a new entity will be saved everytime I save changes.
Thanks for any help!
Edit 1:
I found the problem and I'm not sure what exactly Breeze expects of me. When my ContextProvider saves the Entity it sends a DB-Update-Command (for all the properties) and then returns. The problem is, that the Id of the saveResult-Object is still the "tempId" and not the real one - so when Breeze on Client-Side receives the object it doesn't find it in the entityManager anymore because the Key-Fixup happened already.
What exactly is Breeze expecting to receive from the saveResult? A representation of the object as it is in the Database at this moment? - would make sense but I don't find it documented.
Edit 2:
It looks like I'm not able to replace the object in the saveWorkState.saveMap (EntityInfo info.Entity is readonly). What I would like to do is create the newly added object and return this object instead of the one that breeze sent me. I have calculated values on the newly created object and the new "real" Id. What the NoDB-Sample seems to do is just overwrite the Id for the newId but any other properties are not changing. Maybe I'm not using this mechanism right?
To make sure, that all EntityInfos that are returned in my SaveResult are the latest representation of the Database I just clear the saveWorkState.SaveMap-Lists
saveWorkState.SaveMap[n].Clear()
and add the new representations
saveWorkState.SaveMap[n].AddRange(newEntityInfos);
and for every Entity that is Added or Modified I create a new EntityInfo with the object that is returned by the Database
private EntityInfo SaveEntity(List<KeyMapping> keyMap, EntityInfo info) {
EntityInfo result = info;
switch (info.EntityState) {
case EntityState.Added: {
// CreateInDatabase
// Possible changes in object properties happen (for calculated values)
...
var newObj = GetObjectAgainFromDatabase(info.Entity);
keyMap.Add(new KeyMapping() { EntityTypeName = bu.RuntimeClass.FullName, TempValue = (MyObject)info.Entity.Id, RealValue = newObj.Id });
result = CreateEntityInfo(newObj, EntityState.Added);
break;
}
case EntityState.Deleted: {
// Delete in Database
// EntityInfo doesn't have to change
break;
}
case EntityState.Modified: {
// Update in Database
result = CreateEntityInfo(bu.WrappedPOCO, EntityState.Modified);
break;
}
default: //EntityState.Detached, EntityState.Unchanged
break;
}
return result;
}
Any comments on this solution are welcome!

breeze.js navigation properties not available to custom initializer when importing

When retrieving entities and metadata from the server my custom initializer functions are able to access thier navigation properties as expected. When exporting metadata and entities to localstorage and reimporting them the navigation properties are not available. I have ensured that the ctors and initializers are registered to the metadatastore before import as the methods are called. I took a look at the breeze source and it appears that the initialize functions are called prior to the item being fully populated and attached.
targetEntity = entityType._createEntityCore();
updateTargetFromRaw(targetEntity, rawEntity, dataProps, true);
if (newTempKeyValue !== undefined) {
// fixup pk
targetEntity.setProperty(entityType.keyProperties[0].name, newTempKeyValue);
// fixup foreign keys
if (newAspect.tempNavPropNames) {
newAspect.tempNavPropNames.forEach(function (npName) {
var np = entityType.getNavigationProperty(npName);
var fkPropName = np.relatedDataProperties[0].name;
var oldFkValue = targetEntity.getProperty(fkPropName);
var fk = new EntityKey(np.entityType, [oldFkValue]);
var newFkValue = tempKeyMap[fk.toString()];
targetEntity.setProperty(fkPropName, newFkValue);
});
}
}
*targetEntity.entityAspect._postInitialize();*
targetEntity = entityGroup.attachEntity(targetEntity, entityState);
if (entityChanged) {
entityChanged.publish({ entityAction: EntityAction.AttachOnImport, entity: targetEntity });
if (!entityState.isUnchanged()) {
entityGroup.entityManager._notifyStateChange(targetEntity, true);
}
}
}
if (targetEntity) {
targetEntity.entityAspect.entityState = entityState;
if (entityState.isModified()) {
targetEntity.entityAspect.originalValuesMap = newAspect.originalValues;
}
entityGroup.entityManager._linkRelatedEntities( targetEntity);
}
If I move the post initialize call below the ._linkRelatedEntities call it appears to work exactly as the query materialization
if (targetEntity) {
targetEntity.entityAspect.entityState = entityState;
if (entityState.isModified()) {
targetEntity.entityAspect.originalValuesMap = newAspect.originalValues;
}
entityGroup.entityManager._linkRelatedEntities( targetEntity);
*targetEntity.entityAspect._postInitialize();*
}
Am I missing something here? Is this the designed functionality? I have found a work around by passing in the EntityManager, setting the entityAspect manager and calling loadNavigationProperties however this seems redundant. Any insight fromt he Breeze folks would be greatly appreciated.
Thanks,
Brad
Not sure but this issue might be related to a bug that we just fixed in Breeze 1.4.1 available now.

Breezejs EntityManager MetadataStore and fetchEntityByKey

I have a SPA application (durandaljs), and I have a specific route where I map the "id" of the entity that I want to fetch.
The template is "/#/todoDetail/:id".
For example, "/#/todoDetail/232" or "/#/todoDetail/19".
On the activate function of viewmodel, I get the route info so I can grab the id. Then I create a new instance of breezejs EntityManager to get the entity with the given id.
The problem is when I call manager.fetchEntityByKey("Todos", id), the EntityManager doesn't have yet the metadata from the server, so it throwing exception "Unable to locate an 'Type' by the name: Todos".
It only works if first I execute a query against the store (manager.executeQuery), prior to calling fetchEntityByKey.
Is this an expected behavior or a bug ? Is there any way to auto-fecth the metadata during instantiation of EntityManager ?
note: I believe it's hard to use a shared EntityManager in my case, because I want to allow the user directly type the route on the browser.
EDIT: As a temporary workaround, I'm doing this:
BreezeService.prototype.get = function (id, callback) {
var self = this;
function queryFailed(error) {
app.showMessage(error.message);
callback({});
}
/* first checking if metadatastore was already loaded */
if (self.manager.metadataStore.isEmpty()) {
return self.manager.fetchMetadata()
.then(function (rawMetadata) {
return executeQuery();
}).fail(queryFailed);
} else {
return executeQuery();
}
/* Now I can fetch */
function executeQuery() {
return self.manager.fetchEntityByKey(self.entityType, id, true)
.then(callback)
.fail(queryFailed);
}
};
You've learned about fetchMetadata. That's important. If you application can begin without issuing a query, you have to use fetchMetadata and wait for it to return before you can perform any operations directly on the cache (e.g., checking for an entity by key in the cache before falling back to a database query).
But I sense something else going on because you mentioned multiple managers. By default a new manager doesn't know the metadata from any other manager. But did you know that you can share a single metadataStore among managers? You can.
What I often do (and you'll see it in the metadata tests in the DocCode sample), is get a metadataStore for the application, write an EntityManager factory function that creates new managers with that metadataStore, and then use the factory whenever I'm making new managers ... as you seem to be doing when you spin up a ViewModel to review the TodoDetail.
Coming from a Silverlight background where I used a lot of WCF RIA Services combined with Caliburn Micro, I used this approach for integrating Breeze with Durandal.
I created a sub folder called services in the App folder of the application. In that folder I created a javascript file called datacontext.js. Here is a subset of my datacontext:
define(function (require) {
var breeze = require('lib/breeze'); // path to breeze
var app = require('durandal/app'); // path to durandal
breeze.NamingConvention.camelCase.setAsDefault();
// service name is route to the Web API controller
var serviceName = 'api/TeamData',
// manager is the service gateway and cache holder
manager = new breeze.EntityManager(serviceName),
store = manager.metadataStore;
function queryFailed(error) {
app.showMessage("Query failed: " + error.message);
}
// constructor overrides here
// included one example query here
return datacontext = {
getSponsors: function (queryCompleted) {
var query = breeze.EntityQuery.from("Sponsors");
return manager
.executeQuery(query)
.then(queryCompleted)
.fail(queryFailed)
}
};
}
Then in your durandal view models you can just require the services/datacontext. For example, here is part of a sample view model from my app:
define(function (require) {
var datacontext = require('services/datacontext');
var ctor = function () {
this.displayName = 'Sponsors',
this.sponsors = ko.observable(false)
};
ctor.prototype.activate = function () {
var that = this;
return datacontext.getSponsors(function (data) { that.sponsors(data.results) });
}
return ctor;
});
This will allow you to not worry about initializing the metadata store in every view model since it is all done in one place.

Sproutcore datasources and creating new records with relationships

I'm trying to get my head around datasources and related models in sproutcore and am getting no where fast was wondering if anyone could maybe help me understand this all bit better.
Basically I have two related models Client and Brand, Clients can have many Brands and Brands can have a single Client, I have defined my models correctly and everything is pulling back as expected. The problem I'm having is working out how to create a new Brand and setup its relationship.
So on my Brand controller I have a createBrand method like so:
var brand = DBs.store.createRecord(DBs.Brand, {
title: this.get('title')
}, Math.floor(Math.random()*1000000));
brand.set('client', this.get('client'));
MyApp.store.commitRecords();
So as this is a new Brand I randomly generate a new ID for it (the second argument to createRecord). This is calling my createRecord in my datasource to create the new Brand, and then it also calls the updateRecord for the client.
The problem I'm having is that the clientUpdate is being passed the temporary (randomly generated id) in the relationship. How should I be structuring my creating of the new brand? Should I be waiting for the server to return the newly created brands ID and then updating the client relationship? If so how would I go about doing this?
Thanks
Mark
Right, after sitting in the sproutcore IRC channel and talking to mauritslamers he recommended creating a framework to handle all the server interactions for me manually.
So I setup a framework called CoreIo, which contains all my models, store and data source.
The data source is only used for fetching records from the server ie:
fetch: function(store, query) {
var recordType = query.get('recordType'),
url = recordType.url;
if (url) {
SC.Request.getUrl(CoreIo.baseUrl+url)
.header({ 'Accept': 'application/json'})
.json()
.notify(this, '_didFetch', store, query, recordType)
.send();
return YES;
}
return NO;
},
_didFetch: function (response, store, query, recordType) {
if (SC.ok(response)) {
store.loadRecords(recordType, response.get('body'));
store.dataSourceDidFetchQuery(query);
} else {
store.dataSourceDidErrorQuery(query, response);
}
},
Then the CoreIo framework has creation methods for my models ie:
CoreIo.createBrand = function (brand, client) {
var data = brand,
url = this.getModelUrl(CoreIo.Brand);
data.client_id = client.get('id');
SC.Request.postUrl(url)
.json()
.notify(this, this.brandDidCreate, client)
.send(data);
};
CoreIo.brandDidCreate = function (request, client) {
var json = request.get('body'),
id = json.id;
var ret = CoreIo.store.pushRetrieve(CoreIo.Brand, id, json);
var brand = CoreIo.store.find(CoreIo.Brand, id);
if (ret) {
client.get('brands').pushObject(brand);
}
};
I would then call into these 'actions' to create my new models which would setup the relationships as well.

Resources