You should specify XLOCK-hint on your statement to achieve desired behavior.
Other concurrent transaction can not read or write the selected row.
SELECT *
FROM Employee e
WITH (XLOCK)
WHERE e.id = 123;
Now I want to describe why HOLDLOCK and ROWLOCK won't do a trick.
HOLDLOCK
will inform SqlServer to hold S-locks (shared locks) until transaction ends. S-lock prevents concurrent writes while someting is being read. But it allows to read data concurrently. That's why it called 'shared'. By default, shared locks live as long as it proposed by current isolation level of transaction. For read committed isolation level (which is default) shared lock will be released after data was fully read. That means row-level lock releases just after row was read, page-level lock releases after all data on page was read and table-level lock releases after statement was ended. But HOLDLOCK forces shared locks to be released at the end of transaction. So, when you set HOLDLOCK, shared locks of statement are forced to live longer like it was executed within transaction with repeatable read isolation level.
XLOCK
works just like HOLDLOCK with one difference: X-locks (exclusive locks) are used.
UPDLOCK
forces SqlServer to use U-locks (update locks) inistead of S-locks without any other differences with HOLDLOCK hint. U-lock is a specific lock type for read statements followed by any writes within transaction, what potentially can cause deadlocks. So, you use UPDLOCK when you want restrict reads of the same data during concurrent transactions for update purpose.
ROWLOCK
tells SqlServer to hold locks at row-level even when page- or table-level locks are supposed to be used. Basically, you don't need to specify ROWLOCK if you're not sure what you do, because you can make statement execution more complicated.