user9871834
user9871834

Reputation: 93

ASP.NET Web API C# Concurrent Requests Causing Duplicates in Database

I have a WebApi Async controller method that calls another async method that first does a null check to see if a record exists, and if it doesn't add it to database. Problem is if I have say 3 requests come in at the same time all the null checks happen at once in various threads (i'm assuming) and I will get 2 duplicate entries. For example:

 public async void DoSomething()
{
    var record = {query that returns record or null}
    if (record == null)
    {
        AddNewRecordToDatabase();
    }   
}

... This seems like a very common thing and maybe I'm missing something, but how do I prevent this from happening? I have to purposely try to get it to create duplicates of course, but it is a requirement to not allow it to do so.

Thanks in advance,

Lee

Upvotes: 9

Views: 3838

Answers (3)

John Wu
John Wu

Reputation: 52240

There are several answers to this, depending on the details and what your team is comfortable with.

  • The best and most performant answer it to modify your c# code so that instead of calling a CRUD database operation it calls a stored procedure that you write. The stored procedure would check for existence and insert or update only as needed. The specifics are completely under your control, since you write the code.

  • If you want to stick with ordinary CRUD operations, you can force the database to serialize the requests one after the other by wrapping them in a transaction and using a strict transaction isolation level. On SQL Server you'd want to use serializable. This will prevent any transaction from altering the state of the table in the short time between the part where you check for existence and when you insert the record. See this article for a list of transaction isolation levels and how to apply them in c# code. If you do this there is a risk of deadlock, so you'll need to catch and swallow those specific errors.

  • If your only need it to ensure uniqueness, and the new record has a natural (not surrogate) key, you can add a uniqueness constraint on the key, which will prevent the second insert from succeeding. This solution doesn't work so well with surrogate keys; it doesn't really solve the problem, it just relocates it to the surrogate key generation process. But if you have a decent natural key, this is very easy to implement.

Upvotes: 0

Yuli Bonner
Yuli Bonner

Reputation: 1269

I would solve this by putting unique constraints in the data layer. Assuming your data source is sql, you can put a unique constraint across the columns you are querying by with "query that returns record or null" and it will prevent these duplicates. The problem with using a lock or a mutex, is that it doesn't scale across multiple instances of the service. You should be able to deploy many instances of your service (to different machines), have any of those instances handle requests, and still have consistent behavior. A mutex or lock isn't going to protect you from this concurrency issue in this situation.

Upvotes: 2

Morten Bork
Morten Bork

Reputation: 1632

I prevent this from happening with async calls by calling a stored procedure instead. The stored procedure then makes the check, via a "On duplicate key detection" or a similar query for MSSQL db.

That way, it's merely the order of the async calls that gets to determine which is a create, and which is not.

Upvotes: 1

Related Questions