Considering the following table

The sequence value is a custom auto increment key combining letters and numbers that a particular client need for his system.
We made a function called GetNextSequence() which should return the next value of the sequence. The step to reading and updating the sequence goes as follow
- Read the sequence value using the KeyId:
SELECT Sequence FROM [Key] WHERE KeyId = @Id - Parse the sequence value and determine the next value
- Write the sequence value to the table:
UPDATE [Key] SET Sequence = @Sequence WHERE KeyId = @Id
Here is the C# code (simplified for clarity):
var transaction = connection.BeginTransaction(IsolationLevel.RepeatableRead); var currentSequenceValue = SqlUtils.ExecuteScalar(connection, transaction, "SELECT Sequence FROM [Key] WHERE KeyId = @Id", new SqlParameter("@Id", keyId)); var updatedSequenceValue = ParseSequence(currentSequenceValue); SqlUtils.ExecuteScalar(connection, transaction, "UPDATE [Key] SET Sequence = @Sequence WHERE KeyId = @Id", new SqlParameter("@Id", keyId), new SqlParameter("@Sequence", updatedSequenceValue)); transaction.Commit(); return updatedSequenceValue; Our problem reside in that two different servers can access the same sequence and we end up getting a deadlock
Transaction (Process ID X) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
In C#, I tried to set up different lock combination like a transaction isolation IsolationLevel.RepeatableRead or IsolationLevel.Serializable or in SQL using table hint ROWLOCK and HOLDLOCK, but without success.
I want that each server be able to read, manipulate and update a sequence in a atomic way. What is the proper way to setup a lock for this situation?