Skip to content

Conversation

N-Olbert
Copy link
Contributor

@N-Olbert N-Olbert commented Aug 25, 2025

Summary of the changes (Less than 80 chars)

  • Adjust result builder to not return incomplete data from the entity store
  • Detail 2

Closes #5256 (in this specific format)
Closes #8290
Closes #8063
Probably also #5477

Background:
It was possible that Strawberry Shake returned incomplete data from the entity store when an entity referenced itself but each reference had a different selection set.

For example, consider this query:

query GetSelfishGuy {
  selfishGuy { # returns a Person!
    id # e.g. "1"
    firstName
    lastName
    bestFriend { # returns a Person!
      firstName
      age
      phone
    }
  }
}

Since the person is selfish, its best friend is him-/herself.
Because the entity store is filled recursively, the bestFriend selection set is evaluated first, writing firstName, age, and phone to the entity store for id "1". However, after that the selfishGuy selection set is evaluated and overwrites the entity store entry for "1" with the values of id, firstName, lastName, and the bestFriend-id. As a result age and phone are lost.

This PR fixes the issue by rechecking whether the entity is already present in the store after evaluating the arguments required to create the entity at the current hierarchy level. Therefore, no fields are lost and the entity store is filled correctly. In fact, it is populated step-by-step.

Core change (the args were inlined before):

 if (session.CurrentSnapshot.TryGetEntity(entityId, out global::StrawberryShake.CodeGeneration.CSharp.Integration.RecursiveEntitySelfReference.State.PersonEntity? entity))
 {
     var arg0 = Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "id"));
     var arg1 = Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "firstName"));
     var arg2 = Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "lastName"));
     var arg3 = Update_NonNullableIGetSelfishGuy_SelfishGuy_BestFriendEntity(session, global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "bestFriend"), entityIds);
     var arg4 = Update_IGetSelfishGuy_SelfishGuy_FriendsEntityNonNullableArray(session, global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "friends"), entityIds);
     session.SetEntity(entityId, new global::StrawberryShake.CodeGeneration.CSharp.Integration.RecursiveEntitySelfReference.State.PersonEntity(arg0, arg1, arg2, arg3, arg4, entity.Age, entity.Phone, entity.ZipCode));
 }
 else
 {
     var arg0 = Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "id"));
     var arg1 = Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "firstName"));
     var arg2 = Deserialize_NonNullableString(global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "lastName"));
     var arg3 = Update_NonNullableIGetSelfishGuy_SelfishGuy_BestFriendEntity(session, global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "bestFriend"), entityIds);
     var arg4 = Update_IGetSelfishGuy_SelfishGuy_FriendsEntityNonNullableArray(session, global::StrawberryShake.Json.JsonElementExtensions.GetPropertyOrNull(obj, "friends"), entityIds);
+    // Since arg3 or arg 4 may have changed the entity store, check again if the requested
+    // entity now exists (and if so, us it to merge the values like age, phone etc.)
+     if (session.CurrentSnapshot.TryGetEntity(entityId, out entity))
+     {
+         session.SetEntity(entityId, new global::StrawberryShake.CodeGeneration.CSharp.Integration.RecursiveEntitySelfReference.State.PersonEntity(arg0, arg1, arg2, arg3, arg4, entity.Age, entity.Phone, entity.ZipCode));
+     }
+     else
+     {
         session.SetEntity(entityId, new global::StrawberryShake.CodeGeneration.CSharp.Integration.RecursiveEntitySelfReference.State.PersonEntity(arg0, arg1, arg2, arg3, arg4, default !, default !, default !));
+     }
  }

@TATA-VihaanJoni
Copy link

Hello @michaelstaib and @N-Olbert, what is the status of this branch? We are also suffering same issue, and I confirming that this fix is working.

To hotfix it, we are generating the proejct with StrawberryShake, then copying the generated files, removing StrawberryShake dependency, and then adding the copied files to project normally. Then we are doing the adjustments using this fix. But this is not good way, too much manual effort and not a proper workflow.

If something is missed in this branch, I can try to help in adding, but I am not knowing much of Git or GitHub as we are using SVN only

@N-Olbert
Copy link
Contributor Author

Hi @TATA-VihaanJoni and thank you for your feedback.
I’m not sure when this will make it into main, but Michael can probably help you with that.

In the meantime, a less drastic hotfix could be to either:

  • request the same selection set/fragment for the affected entities
  • turn off the entity store (this may impact performance, remove extend schema @key(fields: ...) from the extensions to do so )
  • split the operation into separate ones

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants