Fundamentally, if you want objects that are in a hierarchy relative to a parent, you need to do a concatenation of the local-to-parent transform with the parent. This is how it works.
However, I think you can have your cake and eat it too: use two transforms: one a local-to-parent transform and one a cached worldspace transform. You also want a dirty flag. When an object moves in world space the local-to-parent matrix, or in your case orientation and translation, is updated and the dirty flag is set. All of the children then have their dirty flags set. Any request for an entities world matrix must first check the dirty flag. If set travel up to the first ancestor that is not dirty and recalculate each descendants world space matrix, clearing their dirty flag, back to the original entity. Then return the new world matrix. This avoids recalculating matrices of top level objects that have not moved or children who are part of a hierarchy that is static this frame. Normally this lazy evaluation is hidden behind the object's GetWorldMatrix() method so the caller never sees it. All the rest of the work is hidden behind SetWorldMatrix( ), SetLocalToParentMatrix(), etc. They do all the right operations under the hood.
I would also note that while storing orientation and translation is cheaper from a storage perspective, it is quite expensive to perform a quaternion to matrix every frame. This solution might help you for objects that do not move often, but I suspect it might actually be more performant to store the local-to-parent transform as a matrix, but I am not sure what kind of transforms you are performing.