cancel
Showing results for 
Search instead for 
Did you mean: 
Read only

CAP; UPDATE method doesn't trigger READ hanlder after function _readAfterWrite

alicegavanelli
Participant
4,442

Hallo Experts, I have an issue about READ handler.

I have an Entity with calculation fields. In order to set these fields, I have implemented READ handler.

Triggering a GET request everythings is ok: fields are calculated.

Triggering a PUT request in order to modify entity’s data, fields are empty.


CAP after update runs _readAfterWrite function; this function re-loads entity’s data from DB correctly. After this function, CAP should trigger my READ handler, but it doesn’t.

As a result my calculated fields are empty.

Any ideas on how to solve this problem?

Best regards.

Alice

Accepted Solutions (1)

Accepted Solutions (1)

alicegavanelli
Participant

Thank you Sebastian Van Syckel,

the right answer is:

/*
 * srv/raw-service.js
 */
const cds = require('@sap/cds')

module.exports = async function() {
  const { ReadAfterWrite } = this.entities //> ReadAfterWriteService.ReadAfterWrite
  const DatabaseService = await cds.connect.to('db')
  // register for ReadAfterWriteService.ReadAfterWrite as this is the target of the SELECT query
  DatabaseService.after('each', ReadAfterWrite, (row) => { //> invoked for each row of a READ result
    row.text2 = 'virtual text'
  })
}

Answers (2)

Answers (2)

vansyckel
Product and Topic Expert
Product and Topic Expert

Hi alicegavanelli,

Your service-level handlers for read shall not be invoked during non-read events, as this could have unintended consequences. However, your use case is certainly valid and we will discuss it.

In the meantime, you could lower your read handler to the database-level, where it would get invoked, like this:

module.exports = cds.service.impl(async function () {
  const DatabaseService = await cds.connect.to('db')
  DatabaseService.after('READ', '<qualifier>.RemoteConnections', function (data, req) {
    for (let row of data) {
      row.description = '<computed value>'
    }
  })
})

Best,
Sebastian

alicegavanelli
Participant
0 Likes

Hi vansyckel,

I've treied to test your solution but this hanlder is never triggered.

I've put a console.log inside my function _setDescriptions and it is never called; both in GET and PUT calls.

Maybe I'm wrong something.

module.exports = cds.service.impl (async function(){
	const DatabaseService = await cds.connect.to('db');
	DatabaseService.on('READ', 'RemoteConnections', _setDescriptions);	
	
	this.before("*", _isAuthenticated );
	this.before("CREATE", "AppUsers", _validateCreateAppUsers );
	this.before("*", _checkRoleBinding );
		
	this.before(["CREATE", "UPDATE"], "AppUsers", _setMailLowerCase );	
	this.before("CREATE", _fillCreatedBY );
	this.before("UPDATE", _fillChangedBY );
	this.before("CREATE", "Messages", _setDefaultMessage );	 
	
	this.on("changeUserPassword", _changeUserPassword);
	this.on("checkRemoteSystem", "RemoteConnections",_checkRemoteSystem);
});

Thank you in advance.

Alice

vansyckel
Product and Topic Expert
Product and Topic Expert
0 Likes

Hi alicegavanelli ,

That was my bad, sorry. You need to register using the fully qualified name of the entity (e.g., "my.bookshop.RemoteConnections"), as the database service may serve multiple service models and, hence, the service-local entity name ("RemoteConnections") may not be unique. I've updated my answer above.

Best,
Sebastian

alicegavanelli
Participant
0 Likes

Hi vansyckel,

thank you for you help, but I've tried also this new solution and unfortunatly It doesn't work.

Event is not triggered yet.

I've tried both this possibility but nothing:

module.exports = cds.service.impl (async function(){
	const db = await cds.connect.to('db');
	db.on("READ", "<qualifier>.RemoteConnections", _setDescriptions);

	........
});
module.exports = cds.service.impl (async function(){
	const db = await cds.connect.to('db');
	const {RemoteConnections} = db.entities("<qualifier>");
	db.on("READ", RemoteConnections, _setDescriptions);	
	
	......
});

If I try test db.BEFORE, it is triggered but the calculation fields are empty.

Thank you for you help.

Best,

Alice

vansyckel
Product and Topic Expert
Product and Topic Expert
0 Likes

Hi alicegavanelli,

"<qualifier>" was meant as a placeholder to make the entity name fully qualified, as I don't know your model.

Example: Given this cds model, you can register a handler for the service entity "CatalogService.Books" or the database entity "sap.capire.bookshop.Books", i.e., the "<qualifier>" is either "CatalogService" or "sap.capire.bookshop".

Best,
Sebastian

alicegavanelli
Participant
0 Likes

Hi vansyckel,

sorry for the misunderstanding, I've put my namespace in the actual code.

Here in the blog I put <Qualifier> just to not show my namespace.

So the problem is real not my misinterpretation of the suggestion.

Now I show you the exactly code.

using {it.conse.wispin.users as wispinUsers } from '../db/users-model';
service WispinService @(path:'/odata/wispinService') {
  @Capabilities: { Insertable:true,Updatable:true, Deletable:true }
  entity RemoteConnections as projection on  wispinUsers.RemoteConnections;
}          
module.exports = cds.service.impl (async function(){
	const db = await cds.connect.to('db');
	db.on("READ", "it.conse.wispin.users.RemoteConnections", _setDescriptions);

	........
});

Or

module.exports = cds.service.impl (async function(){
	const db = await cds.connect.to('db');
	const {RemoteConnections} = db.entities("it.conse.wispin.users");
	db.on("READ", RemoteConnections, _setDescriptions);	
	
	......
});

Thank you in advance.

Alice

vansyckel
Product and Topic Expert
Product and Topic Expert
0 Likes

Hi alicegavanelli,

My bad again, sorry! It needs to be db.after not db.on, as you want to augment the result and not override the result fetching itself. I'll update my answer above.

BTW, The reason why your code is not called if using db.on is that the on phase is an interceptor stack and you'd need to prepend your handler (db.on(...) appends).

Best,
Sebastian

alicegavanelli
Participant
0 Likes

Hi vansyckel,

thank you for you help but unfortunatly this solution doesn't work.

Problems are:

  • handler is triggered only for CREATE or UPDATE events. If I put only READ handler is not trigger because my call is PUT (change) or POST (create).
  • however, evenif my handler is triggerd with the below code, data in the oData's response is not correct. Descrition is not set
const db = await cds.connect.to('db');
const {RemoteConnections} = db.entities("it.conse.wispin.users");
db.after(["READ","CREATE","UDPATE"], RemoteConnections, async function(data, req) {
      " Set my description
}
)
  • your suggestion is below but row has no property description; it has data.results as an object array data.results[0][2] = 'my description';
function (data, req) {
    for (let row of data) {
      row.description = '<computed value>'
    }
  })

So my problem is the result of PUT and POST event that are not changed.

thank you.

Alice

vansyckel
Product and Topic Expert
Product and Topic Expert

Hi alicegavanelli,

Please see the working example below.

Best,
Sebastian

/*
 * db/data-model.cds
 *
namespace my.bookshop;

entity ReadAfterWrite {
  key ID        : Integer;
  text1         : String;
  virtual text2 : String;
}
*/

/*
 * srv/raw-service.cds
 *
using my.bookshop as my from '../db/data-model';

service ReadAfterWriteService {
  entity ReadAfterWrite as projection on my.ReadAfterWrite;
}
*/

/*
 * srv/raw-service.js
 */
const cds = require('@sap/cds')

module.exports = async function() {
  const { ReadAfterWrite } = this.entities //> ReadAfterWriteService.ReadAfterWrite
  const DatabaseService = await cds.connect.to('db')
  // register for ReadAfterWriteService.ReadAfterWrite as this is the target of the SELECT query
  DatabaseService.after('each', ReadAfterWrite, (row) => { //> invoked for each row of a READ result
    row.text2 = 'virtual text'
  })
}

/*
 * HTTP
 *
PUT http://localhost:4004/read-after-write/ReadAfterWrite(1)
Content-Type: application/json

{ "text1": "text on db" }

response:
{
  "@odata.context": "$metadata#ReadAfterWrite/$entity",
  "ID": 1,
  "text1": "text on db",
  "text2": "virtual text"
}
*/
alicegavanelli
Participant
0 Likes

Thank you very much vansyckel,

your solution works!!!!!!

Best.

Alice

alicegavanelli
Participant

With a PUT call, CAP triggers these events in that orders:

  1. srv.ON (event UPDATE)
  2. srv.AFTER (event UPDATE)
  3. _readAfterWrite

Never call after-READ or on-READ.