Google App Engine Datastore: How to get entity by ID/Name if parent key is unknown?
Asked Answered
S

6

26

There are two kinds of entity: User and Trip. User is parent to Trip and Trip is child to User.

For privacy consideration I am POSTing only Trip ID/Name. Because it is looks like a Trip Key contains encoded User ID/Name.

How to get entity by ID/Name if parent key is unknown?

Stoplight answered 13/1, 2013 at 8:38 Comment(5)
+1 - this is a perfectly valid question. Whoever put -1 - care to explain?Noncompliance
Why you don't use search? [see][1] [1]: #12676164Appointment
@Lapteuh - Did you even look at the answer that you are refering to? The query they propose requires full parent key (kind+id/name) and this is exactly what OP does not have.Noncompliance
So, what do we do? Send the key values to client and retrieve the object with that key?Iridize
@Iridize I think what should happen is the User ID get stored on the session, then you pass the Trip ID to the client. So, when the client wants to update a trip, the Trip ID is sent back to the server, which then gets the User ID from the session, to create the full Trip key. Eg: Key userKey = KeyFactory.createKey(TABLE_USER, userId); Key tripKey = KeyFactory.createKey(userKey, TABLE_TRIP, tripId);Orometer
N
19

You can't. Parent key is part of the entity key and you need a full key to get an entity.

Also query with key filter won't find entities with parents unless you specify the ancestor key.

Noncompliance answered 13/1, 2013 at 10:36 Comment(0)
E
1

If you create all of your "User" entities under a "Root" entity and add a "uuid" property to your "Trip" entity, you can look for the single "Trip" with the specified UUID.

Filter uuidFilter = new FilterPredicate("uuid", FilterOperator.EQUAL, uuid.toString());
Query q = new Query("Trip").setAncestor(root.getKey()).setFilter(uuidFilter);
Expansionism answered 25/7, 2013 at 6:49 Comment(0)
A
0

@peter-knego In initial question: User is parent for Trip. To get entity by id you need reconstruct key with parent to get full key. But you may avoid this, just allocate ids for full key of Trip. And you may construct full key with allocated id. This is my logic.

Appointment answered 15/1, 2013 at 10:1 Comment(0)
T
0

You can do something like this:

public Entity GetEntity(String kind, String idName) 
        throws EntityNotFoundException{
    Key key = KeyFactory.createKey(kind, Long.parseLong(idName));
    return datastore.get(key);
}
Triplenerved answered 29/9, 2013 at 18:54 Comment(1)
Nope, it won't find anything unless you specify parent in key.Garman
S
0

I worked out 3 alternatives that may solve this issue, which is a very important one IMHO.

---- 1st alternative ----

If your Trip's Id is calculated from another attribute, there is a way. Instead of getting the Trip by its id get it from that other calculated property. Let's imagine your Trip's id is calculated by some canonical name (A URN you infer from its full title), e.g. if the Trip's full name is

Voyage to the Everest

your canonical name may be voyage-to-the-everest and this is the String you use as name for the Key. So instead of getting the element using datastore.get use:

@Override
public Optional<Trip> findById(String tripCanonicalName) {
   StructuredQuery.PropertyFilter eqTripCanonicalName = StructuredQuery.PropertyFilter
                    .eq("canonicalName", tripCanonicalName);

   EntityQuery query = Query.newEntityQueryBuilder().setKind("Trip")
                    .setFilter(eqTripCanonicalName).setLimit(1).build();

   QueryResults<Entity> results = getDatastoreService().run(query);

   if (results.hasNext()) {
       return Optional.of(fromEntity(results.next()));
   }

   return Optional.empty();
}

this will get the entity (Trip) no matter whose parent (User) be.

---- 2nd alternative ----

Before accessing an element probably you first have to list them and then select one and go to an access link. As we know using the id of the task wont be enough because it will be unique only for its parent (User), but instead of showing that id you may use the url safe id:

entity.getKey().toUrlSafe()

so in the conversion from entity to object assign the Task element this id encoded in a base-64 encode. To get the key back from the url safe use

Key.fromUrlSafe

It will guarantee you will always gonna use a global unique id.

---- 3rd alternative ----

Using HATEOAS you can specify the link for accesing the Task, so if the task has some id such as parentId or userId which is basically getting his parent node's id, it could be very easy for you to stablish a link pointing to a url like this

http://base-url.com/users/{userId}/tasks/{taskId}

So in a HATEOAS request this could be indicated in the links, that indicates the allowed actions for the element, so for viewing the element use self, e.g

{
  "id": "voyage-to-the-everest",
  "name":"Voyage to the Everest",
  "userId": "my-traveler-user-id",
  "_links":{
    "self":{
      "href":"http://localhost:8080/users/my-traveler-user-id/tasks/voyage-to-the-everest
    }
  }
}

If instead of a userId you use a parentId you may work it out with an interface where all the nodes specify if they have a parent or not. Even it could be more flexible with a parent property where you define the whole parent hierarchy:

public interface DatastoreNode{
  String getParentId();
  String getParentKind();
  String getParentUrlTag();
  DatastoreNode getParent();
}

Although HATEOAS is strongly recommended you can infer the same url having a json structure such as

   {
      "id": "voyage-to-the-everest",
      "name":"Voyage to the Everest",
      "parent": {
           parentKind: "User",
           parentId: "my-traveler-user-id",
           parentUrlTag: "users",
           parent: {}  
       }
    }
Sha answered 3/5, 2017 at 23:35 Comment(0)
S
0
trip_query = datastore_client(kind='Trip')
trips = list(trip_query.fetch())
for trip in trips:
    # .key.id_or_name
    print(f'trip id/name:{trip.key.id_or_name}')
Sevier answered 6/8, 2022 at 12:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.