I'm trying to implement odata v4 for my current master detail app which was using odata v2.
In my app, I used some statements like these.
this.getOwnerComponent().getModel().metadataLoaded().then(fnSetAppNotBusy);
var oContext = this.getModel().createEntry(sPath, {
properties: {
Name: "ABC",
Age: "20"
},
success: this._fnEntityCreated.bind(this),
error: this._fnEntityCreationFailed.bind(this)
});
I see in this document Changes Compared to OData V2 Model, odata v4 has replaced metadataLoaded with "corresponding methods".
I have searched and found some but am not sure if these methods are correct. Can someone please confirm?
And I can't find method createEntry in oDataModel v4, so how can I implement the same logic as I did with odata v2?
Thanks
Hmm. The creation is covered in this example:
https://sapui5.hana.ondemand.com/sdk/#/sample/sap.ui.core.tutorial.odatav4.06/code
(Basically, you execute the creation on binding/context level)
As for onMetadataLoaded, you may use getMetaModel() and then use the „appropriate” functions with promises, but I’d go as follows: grab your binding, which is the one you are waiting for, and attach to the event dataReceived. E.g.,
oTable.getBinding('items').attachEvent('dataReceived', function(){...})
The API docs explicitly states that this event is to be used for busy indicators:
https://sapui5.hana.ondemand.com/sdk/#/api/sap.ui.model.odata.v4.ODataListBinding/events/dataReceived
Related
I implemented the update operation for an oData service. When I tried to call it via my SAPUI5 application, I got the following error message:
'XXX_GET_ENTITY' not implemented in data provider class
That's true - I did not yet implement the GET ENTITY method.
However, can someone tell me why I need this one for an update?
The JS coding for the service request is created this way:
oDataModel.update("/EntitySetName(<key>)", oPayload, {
success: function(oData) {
...
},
error: function(oError) {
...
}
});
I appreciate every hint / explanation.
The default update method for the ODataModel is a patch/merge, see the documentation from the ODataModel class:
Trigger a PUT/MERGE request to the OData service that was specified in
the model constructor.
The update method used is defined by the global defaultUpdateMethod
parameter which is sap.ui.model.odata.UpdateMethod.Merge by default. [...]
The default implementation of the PATCH_ENTITY method calls the READ_ENTITY first and then merges the incoming data with the retrieved data to allow for partial updates. From the comments in this method:
*-a patch request is a partial update of an entity. All provided components are patched.
*-The default implementation of patch_entity performs a read before update [...]
To do a PUT request, set the updateMethod property to sap.ui.model.odata.UpdateMethod.Put.
I'd like to pass expand parameters to read because it doesn't work if I call the service like this:
oModel1.read("/LinesSet?$expand=ToCells", {
The read API awaits a map of options as a second argument in which we can define any query using the property urlParameters:
oModel1.read("/LinesSet", {
urlParameters: {
"$expand": "ToCells",
"$select": "LineID,ToCells/CellID,...", // reduce data load
},
filters: [ // Filter required from sap/ui/model/Filter
new Filter({/*...*/}), // reduce data load
],
success: this.onSuccess.bind(this),
// ...
});
⚠️ Please note that loading large amounts of data significantly affects memory consumption and UX negatively. This might even lead to crashing the application altogether ultimately. See the section Loading Large Amounts of Data from the documentation.
Whenever you use methods like [...] sap.ui.model.odata.v2.ODataModel#read [...] in application code, your application must not load large amounts of data.
⚠️ read is a low-level API from the application's point of view. There are other APIs and approaches that can help reducing the amount controller code.
Alternative (better) solution
I'd like to emphasize that v2.ODataModel#read is often not required. You can simply make use of the OData Context/ListBinding by assigning the corresponding name of the <NavigationProperty> to the control in XML:
<Table binding="{ToThatRelatedSingleEntity}" items="{ToThatRelatedCollection}" growing="true">
(Note: You might have to add templateShareable to the aggregation binding accordingly as explained in the topic: Lifecycle of Binding Templates)
The binding, not the application, will then prepare a request automatically for you. No need to use an intermediate JSONModel. Same with v4.ODataModel which doesn't even have the read method.
This makes also migrating to OData V4 much easier.
Is there a way for the SAPUI5 TreeTable to not call the OData web service on each node expansion? I've tried setting the operationMode parameter to client as part of the data binding, but it still retrieves the data via individual web service calls for each node expansion. Currently running SAPUI5 1.28.45.
The model is of type sap.ui.model.odata.v2.ODataModel and set globally. I've tried configuring the default operation mode on the model without success and toggling batch mode without success. It's set up for two-way binding as well.
I'm currently setting the operation mode as below with no luck:
this._oTable.bindRows({
path: '/EventSet',
filters: self._aFilters,
parameters: {
countMode:'Inline',
operationMode: sap.ui.model.odata.OperationMode.Client // Seemingly ignored
}
});
You could try to use JSONModel Binding instead of ODataModel Binding.
You can get the content of the EventSet with a Read function https://sapui5.hana.ondemand.com/#docs/api/symbols/sap.ui.model.odata.v2.ODataModel.html#read
Then in the Success function you take the result, create the JSONModel, convert the data into something that the TreeTable can use, set this to the JSONModel, set the JSONModel to your TreeTable Object (eg.):
this._oTable.setModel(oYourJsonModel, sModelName);
this._oTable.bindRows({
path: "/",
parameters: {
arrayNames: ["additionalData"]
}
});
Here you can find information about JSON Tree Binding:
https://sapui5.hana.ondemand.com/explored.html#/sample/sap.ui.table.sample.TreeTable.JSONTreeBinding/preview
Hope it helps.
Best regards,
Adrian
I am playing around with a OData service and I am very confused when to use this
var oModel = new sap.ui.model.odata.ODataModel("proxy/http/services.odata.org/V3/(S(k42qhed3hw4zgjxfnhivnmes))/OData/OData.svc");
this.getView().setModel(oModel);
vs
var oModel = new sap.ui.model.odata.ODataModel("odatserviceurl", true);
var productsModel = new JSONModel();
oModel.read("/Products",
null,
null,
false,
function _OnSuccess(oData, response) {
var data = { "ProductCollection" : oData.results };
productsModel.setData(data);
},
function _OnError(error) {
console.log(error);
}
);
this.getView().setModel(productsModel);
I have two working example using both approach but I am not able to figure out why using read method if I can achieve same with first version. Please explain or guide me to the documentation which can clear my confusion.
Ok, lets start with the models:
JSON Model : The JSON model is a client-side model and, therefore, intended for small datasets, which are completely available on the client. The JSON model supports two-way binding. NOTE: no server side call is made on filtering, searching, refresh.
OData Model : The OData model is a server-side model: the dataset is only available on the server and the client only knows the currently visible rows and fields. This also means that sorting and filtering on the client is not possible. For this, the client has to send a request to the server. Meaning searching/filtering calls odata service again.
Now, lets look at scenarios where we will use these models:
Scenario 1: Showing data to user in a list/table/display form. Data manipulation is limited to searching and filtering. Here, I would use oData model directly to controls as only fetching of data is required.( your method 1) (NOTE: One way binding). Remember here all changes require a call to server.
Scenario 2: I have an application which has multiple inputs, user can edit changes, also some fields are calculated and mandatory. All in all, many user changes are done which may be temporary and user might not want to save them. Here, you dont want to send these temporary changes to backend as yet. You way want to manipulate, validate data before sending. Here, we will use JSON Model after reading data from odata model ( your method 2). Store the changes in local JSON model, validate and manipulate them and finally send the data using Odata create/update. Remember here all changes DO NOT require a call to server as data is present in local JSON MODEL.
Let me know if this helps you. :)
EDIT : Additional Information :
As per your comment :
Documentation says oModel.read' trigger get request but new sap.ui.model.odata.ODataModel("proxy/http/services.odata.org/V3/(S(k42qhed3hw4zgjxfnhivnmes))/OData/OData.svc")` does the same thing so why and when to use oModel.read
Here, is where you misunderstood. The code
new sap.ui.model.odata.ODataModel("proxy/http/services.odata.org/V3/(S(k42qhed3hw4zgjxfnhivnmes))/OData/OData.svc") will NOT send a read/get Request. It calls the odata services and fetches the metadata of the service. A service can have multiple entities.
For example: the service :http://services.odata.org/Northwind/Northwind.svc/ has mutiple entity sets such as Categories, Customers, Employees etc. So, when I declare : new sap.ui.model.odata.ODataModel("http://services.odata.org/Northwind/Northwind.svc/") it will fetch the metadata for service (not actual data). Only when you call the desired entity set, it will fetch the data. The Entity set is specified :
When you call the read method ( like you have specified '/Products')
Bind the entity set name directly to control like to List,Table etc ( items='{/Products}' )
The object extraMetadata is undefined and throwing an error on line 247 of breeze.labs.dataservice.sharepoint.js
rawEntity.__metadata = { 'type': aspect.extraMetadata.type };
I suspect it is because I have not defined the type found in __metadata object on my entity definitions for breeze. Any suggestions on how to define my type correctly would be very welcome! Here is my type definition for one of the objects.
models.Project = {
name: 'Project',
defaultResourceName: 'getbytitle(\'Projects\')/items',
dataProperties: {
ID: {
type: breeze.DataType.Int32
},
Title: {
nullable: false
},
StatusId: {
type: breeze.DataType.Int32,
nullable: false
},
SelectedApproverId: {
type: breeze.DataType.Int32,
nullable: false
},
Created: {
type: breeze.DataType.DateTime
},
Modified: {
type: breeze.DataType.DateTime
}
},
navigationProperties: {
Status: {
type: "Status",
foreignKeyNames: ['StatusId'],
hasMany: false
},
SelectedApprover: {
type: "User",
foreignKeyNames: ["SelectedApproverId"]
}
}
};
UPDATE: 11/11/2013
If I run the following query:
return breeze.EntityQuery
.from(metadataStore.getEntityType('Project').defaultResourceName)
.orderBy('Created desc')
.using(manager)
.execute()
.then(function (data) {
console.log(data.results);
return data.results;
});
the results are an array of simple JavaScript objects, not Breeze Entities, that lack an __metadata properties. I'm trying to figure out why this is the case.
Update: 11/12/2014
I have confirmed that this issue presents itself when I have multiple entities defined under navigationProperties.
Please be sure you are using BreezeJS v.1.4.12 or later.
To be clear, the code to which you refer is on line 147 (not 247) of the breeze.labs.dataservice.sharepoint.js file in my possession.
It's located within the _createChangeRequest where it is preparing to save a modified entity. I'll assume that you have queried a Product entity, made changes to it, and are saving it back when the error occurs.
I don't believe the problem will be traced to how you defined the metadata for your Product type.
You should NOT define a __metadata property for your type. The __metadata property is something we expect SharePoint (any OData source in fact) to add to the JSON entity data that it sends to the client when you query that OData source for entities.
__metadata wouldn't be defined for results returned by a projection but then your issue concerns a modified entity so I'm assuming that you acquired this entity through a normal query ... one that did not have a select clause.
I'd like to know if you see the __metadata property in the JSON payload of a query that retrieved the entity you were modifying. Please examine the network traffic from the query request. If you don't see it there, we have to find out why the server didn't send it.
Background
The __metadata property on the JSON node is a crucial part of the contract with the SharePoint OData server. That's how the Breeze client learns about the entity's type and its etag.
Look at the jsonResultsAdapter.visitNode and updateEntityNode methods. You'll see how the adapter uses __metadata to determine the EntityType for that data. You'll also see that the adapter moves the __metadata to the adapter result's extraMetadata property. BreezeJS subsequently moves that "extra metadata" from this result object to the entity's entityAspect.extraMetadata property.
Does this seem tortured? It is tortured. OData requires extra information to be carried around with the entity (specifically the etag) without which the server simply will not update or delete the entity. We have to squirrel that info away somewhere, out of your hair, and then bring it back when we make save requests to the server. We put it on the entityAspect in keeping with that property's role as the keeper of the "entity-ness" that has nothing to do with your object's business purpose and everything to do with how it is persisted.
So much for the why. Where is the bug?
The bug
The underlying bug is that this __metadata from the SharePoint OData source has disappeared. We don't know how it disappeared yet. But we're in big trouble without it.
The sharepoint adapter should give a better message when extraMetadata is missing. We actually look for that problem a few lines later; see adjustUpdateDeleteRequest:
var extraMetadata = aspect.extraMetadata;
if (!extraMetadata) {
throw new Error("Missing the extra metadata for an update/delete entity");
}
That test appears too late. I'll make a note to move the test up.
But such a fix will only cause the save to fail with a better message. It won't tell you how to fix it.
So let's work on finding where the __metadata disappeared ... starting with whether it ever arrived in the first place.
I await your report.
Update 17 July 2014
I'm still waiting to hear if you are seeing the __metadata property in the payload of the response to the original entity query.
Meanwhile, I checked the OData specs (plural) for references to the __metadata property. It appears that the __metadata property has always been optional. It follows that an OData provider need not send or honor the etag ... and we know that this is possible because Web API 2 OData didn't support etags ... a defect soon to be corrected.
See the OData v.2 spec where it describes JSON format. Search for the term "__metadata".
The OData v.3 spec also calls for the __metadata property in a JSON response (at least a JSON verbose response).
But ... heavy sigh ... it appears that the __metadata property is gone from the v.4 spec and that the metadata information is supplied entirely through JSON annotations. The DataJS library (used by many but not all BreezeJS OData adapters) may map those annotations into the node's __metadata property but I can't confirm it yet. We have some work to do coping with all of these variations.
In the meanwhile, I think all BreezeJS OData dataservice adapters should take a more defensive position regarding extra metadata and should simply ignore the omission rather than throw exceptions.
We'll make these defensive changes very soon.
Of course the server will reject your update or delete request if the OData service actually requires an etag or other metadata. I don't know what we can do about that just yet.
There hasn't been a post in a while but I am going to share what I found as the problem and how I resolved it for me (because it took me a long time).
Basically the breeze.labs.dataservice.sharepoint adapter has a function serverTypeNameToClientDefault() that expects the SharePoint custom list type as returned by REST/OData in the __metadata "type" field to be in the exact format of:
SP.Data.**mylistname**sListItem** (notice the "sListItem" suffix; ; Ex. SP.Data.CustomersListItem)
This function does a string regex to extract the Breeze entity name from the SharePoint type and uses that name to look up the entity in the metadata store ("Customer" in the above example). If there is no match, Breeze will not find your entity and will return a basic object instead of a Breeze entity. Therefore your REST JSON result returned from SharePoint, even though it does have the __metadata property is not converted into a Breeze entity that contains the property entityAspect.extraMetadata, among other things. This is what leads to the error "Unable to get property 'type' of undefined or null reference"
For my solution, since in my case I don't care as much what the URL of my custom lists are, I just made sure that when my custom list was provisioned by SharePoint that it resulted in a name according to what Breeze expects. You accomplish this by setting the Url attribute of the ListInstance element like this:
<ListInstance
Title="My Customers"
OnQuickLaunch="TRUE"
TemplateType="10000"
Url="Lists/Customers" <!-- List/Customer will not work -->
Description="My List Instance">
...
The better solution would be to make the serverTypeNameToClientDefault() function more robust or fix it to my needs locally but hopefully this can be addressed in a future version of the adapter.
Note that I have tested this solution with the following configurations (not all dependencies listed):
Breeze.Client 1.4.9 with Breeze.DataService.SharePoint 0.2.3
Breeze.Client 1.5.0 with Breeze.DataService.SharePoint 0.3.2
Also note that the 0.3.2 version of the adapter now displays a better error message when this happens as mentioned above -- "Missing the extra metadata for an update/delete entity"; but it doesn't fix the problem.
Hope this helps someone.
For breeze v1.4.14 and breeze labs sharepoint 2013 v0.2.3 i am using small fix in file breeze.labs.dataservice.sharepoint.js.
At the end of function
function visitNode(node, mappingContext, nodeContext)
just before
return result;
i just set property extraMetadata like this:
result.extraMetadata = node.__metadata;
This seems to fix problem when i try to save modified entity back to sharepoint.
Sorry folks for the long overdue aspect of this, but I got the bug with the extra "s" resolved today... FINALLY. You can track the issue here: https://github.com/andrewconnell/breeze.js.labs/issues/6
This all stemmed from a very incorrect assumption I made. It's been fixed in version 0.6.2 of the data service adapter for SharePoint. Note that you MUST use the same name for your entity when creating it in the metadata store as the list where the data is coming from.
I resolved my issue with multiple navigationProperties on an entity by editing line 319 of breeze.labs.dataservice.sharepoint.js v.0.10.0
I changed:
if (entityType._mappedPropertiesCount <= Object.keys(node).length - 1)
to:
if (entityType.dataProperties.length <= Object.keys(node).length - 1)
It looks like the _mappedPropertiesCount includes the navigationProperties count too. e.g. dataProperties.length + navigationProperties.length
The query node was then thought to not contain a full set of properties for the entity (it was assumed to be the result of a partial projection).
It therefore wasn't being treated as an entity, its metadata wasn't being set, and it ultimately wasn't being added to the cache.
It worked with only one navigationProperty as there were two extra items in Object.keys(node), __Metadata and ID. So it would still pass the test with one navigationProperty, but not two or more.