how to implement read-only rows or snapshots of rows In my application I have objects with 3 levels of hierarchy. To model this in SQL Server, I have a ParentObject table with an Id column, ChildObject table with an Id and a foreign key ParentObjectId, and a GrandchildObject table with an Id and a foreign key ChildObjectId. A ParentObject may have many ChildObjects and a ChildObject may have many GrandchildObjects.
At any point in time, users would like to take a snapshot of the current state of a specific ParentObject. A snapshot of a ParentObject may not be modified after it has been created but the snapshot ParentObject can be deleted. The ParentObject snapshot needs to retain the entire object hierarchy, i.e. include ParentObject, ChildObjects, GrandchildObjects. There can be multiple snapshots of a particular ParentObject. All snapshot ParentObjects and the current version of ParentObject must be SELECTable. How can this be implemented using SQL Server 2019?
Users do not have direct access to my database. Instead they call a REST API which has its own account to access my database. Since there is only 1 account accessing the database, I don't think Row-Level Security can fulfil my requirements.
One implementation I have considered is to have a [ParentObject].[SnapshotTime] DATETIME2 NULL column and for the 3 object tables to each have a [IsReadOnly] BIT NOT NULL column. When a user wants to snapshot a ParentObject, I copy the ParentObject row to a new row, setting newRow.SnapshotTime = currentTime and newRow.IsReadOnly = 1. I then cascade copy the ChildObjects and GrandchildObjects, setting the new rows's IsReadOnly = 1. When an UPDATE or DELETE is requested, I check whether a row's IsReadOnly = 0 before performing or denying the action.
Is this a sensible approach? Are there any built-in SQL Server features to achieve my aims? Are there better/faster/more efficient approaches?
You could make them Temporal Tables, and add a ParentObject.SnapshotTime column.  Then every time you update that column, you'll get a new version of the parent object, and you can issue temporal queries on the related tables.