on 2023 Jul 10 10:01 AM
Hi CAP Experts,
I have a task that I thought would be super easy. I have two entities connect via an Association. When calling the associated entity, I want to get the Key in my service handler. Initially I thought I will find this key somewhere in the req object, but I couldn't. Here is my demo project:
bookshop data-model.cds
namespace my.bookshop;
entity Books {
key ID : Integer;
title : String;
stock : Integer;
content : Association to Content @assert.target;
}
entity Content {
key ID : Integer;
content : String
}
cat-service.cds
service CatalogService {
entity Books as projection on my.Books;
entity Content as projection on my.Content;
}
cat-service.js
module.exports = async srv => {
const { Content } = srv.entities
srv.on('READ', Content, (req, next) => {
console.log("What is the current Content ID?")
return next()
})
}
And this is how I call the Content entity via my Books entity:
### Get content via books
GET http://localhost:4004/catalog/Books(201)/content
Content-Type: application/json
I would expect, as I'm successfully following my association, I would also have its key somewhere in my req object. But the only thing I could find is the parents key in req.params. So I could do another select on the parent entity to get the child's key, but as I'm already there, it seems unnecessarily complicated.
Is there a better way? Or am I just blind? 🙂
Thanks!
Nico
Hi nicorunge
You are not blind! It isn't there because it cannot be determined without a roundtrip to the database.
req.subject gives you the path in CQN. In your case it should be something like:
{
"ref": [
{
"id": "CatalogService.Books",
"where": [{ "ref": ["ID"] }, "=", { "val": 201 }]
},
"content"
]
}
So you can do something like:
const { ID } = await SELECT.one.from(req.subject).columns('ID')
But that's as convenient as it gets due to performance considerations.
Best,
Sebastian
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi vansyckel,
thanks, while checking the req object, I missed the subject. Or at least I did not look at it closely. This query indeed helps to provide the right ID.
Unfortunately, the solution still leads to a somewhat cumbersome coding. Although I want to do the same thing, when calling the Content endpoint directly or when coming via Association, I still have to difference that in my handler. When calling my endpoint directly, I have the ID in req.data.id, when coming via Association I have to do this extra query to get this ID.
I probably don't understand enough how the CAP framework performs this Association under the hood, but wouldn't it make sense to also populate req.data.id when navigation through an Association? In the Book handler, the Content_ID must have been read already to follow the association correctly. Therefore, I could imagine that this could be possible without further database access?!
Best regards,
Nico
Hi Nico,
> In the Book handler, the Content_ID must have been read already to follow the association correctly.
Actually, no, in that case we use a subselect to retrieve the correct data, roughly like this:
SELECT * FROM Contents WHERE EXISTS SELECT 1 FROM Books WHERE Books.ID = <UUID> and Books.Content_ID = Contents.ID
I agree that it would be nice to have the info readily available in req.data, but that would mean we'd have to do the database roundtrip every time, even if the information is never needed. Hence, we try to maximize convenience and performance by adding things like req.subject.
Best,
Sebastian
Try the "after" handler and not the "on" handler.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Or, if you need the ID in the on-handler, you can also await the next-function for an earlier result:
module.exports = async (srv) => {
const { Content } = srv.entities;
srv.on('READ', Content, async (req, next) => {
console.log('What is the current Content ID?');
const result = await next();
console.log(`My ID is ${result.ID}!`);
return result;
});
};
You then just have to return the result, for the after-handlers.
Thank you both for your suggestions. I also thought of both, but couldn't believe that it is necessary.
In reality, I'm not only querying the content entity, but also just this one specific content field, like this:
### Get content via books
GET http://localhost:4004/catalog/Books(201)/content/content
Content-Type: application/json
Like it is done in this media sample. And if just the content field is requested, I exactly do not want to go through the standard handler, because I don't need the data in this case.
Also, the Content handler is of course called directly.
### Get content directly
GET http://localhost:4004/catalog/Content(1)/content
Content-Type: application/json
In this case req.data contains the value {ID:1}. Wouldn't it make sense to have the same data provided when coming from an Association?
User | Count |
---|---|
70 | |
10 | |
9 | |
6 | |
6 | |
6 | |
6 | |
5 | |
5 | |
5 |
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.