Do not use dynamic SQL. Instead, call a proc that can do both user existence checking AND error handling. For example:
BEGIN TRY
IF (EXISTS(
SELECT *
FROM ADBregister -- see note below about NOLOCK
WHERE fuldeNavn = @fuldeNavn
)
)
BEGIN
;THROW 50005, 'FullName already taken!', 2
END
INSERT INTO INTO ADBregister (fuldeNavn, [password], borger_cprnr, kontaktPersonNummer)
VALUES (@fuldeNavn, @password, @borger_cprnr, @kontaktPersonNummer)
END TRY
BEGIN CATCH
-- possible additional error handling logic
;THROW;
RETURN
END CATCH
In the C# code, put the Execute in a try / catch(SqlException) where the catch block will look for both the custom error you did in the THROW as well as a more generic UNIQUE CONSTRAINT Violation error that will result in cases where this thread successfully passes the IF EXISTS at the same time another thread is committing the FullName that is being requested here. But the IF EXISTS should catch most cases.
NOTE about NOLOCK:
It is possible to have the IF EXISTS catch even more cases that are happening at the same millisecond in a highly transactional system by adding "WITH (NOLOCK)" to the FROM clause, but there are two issues with this:
- This will catch some entries being committed yes, but it will also
produce false positives by catching entries that were attempting to
commit but get rolled back for some reason (i.e. Dirty Reads). In
that case the FullName would technically be available as the other
thread did not commit.
- It won't catch all instances. There is no
way to catch all instances so even if you increase the chances of
catching in-use entries via NOLOCK, you still need to trap the
UNIQUE CONSTRAINT violation that will occasionally happen, so you
didn't save any coding / logic.
NOTE about MultiThreading:
This issue is not specific to multithreading if you have a system that can EVER have more than one process at the same time connecting to the Database. If you have a desktop app that uses a local DB (hence truly single-user), then you don't need this approach. But if it is a shared database and some other person can try to insert a new user at the same time, then even if your code is using a single thread it will still need this approach. Even if the environment is not highly transactional, it can still be that two people try to do the same thing at the exact same time, even if it is the only two actions taken by the program on a given day.
UserSaved(false)incatchblock., but alsoUserSaved(true)infinallyblock, which means both will be called. Doesn't seem intentional or at least not clear.