I'm trying to keep data consistency in my Azure Sql Server database and implemented two scenarios:
- check constraint
- insert/update trigger
None of them work and I still able to reproduce a situation when my check is bypassed. The rule is simple - there couldn't be more than one active assignment for a user
.
Tasks:
- Id
- UserId
- Status ('Active', 'Done')
User
- Id
- ...
Approach #1 - Check Constraints
I've implemented a function to ensure data consistency and applied it as a check constraint
create function [dbo].[fnCheckUserConcurrentAssignment]
(
@id nvarchar(128),
@status nvarchar(50), -- not used but required to check constraint
)
returns bit
as
begin
declare @result bit
select @result = cast(case when (select count(t.Id)
from dbo.Tasks t
where t.[UserId] = @id
and t.[Status != 'Done') > 1
then 1
else 0
end as bit)
return @result
end
alter table dbo.Tasks
with check add constraint [CK_CheckUserConcurrentAssignment]
check (dbo.fnCheckUserConcurrentAssignment([UserId], [Status]) = 0)
Approach #2 - Trigger
alter trigger dbo.[TR_CheckUserConcurrentAssignment]
on dbo.Tasks
for insert, update
as begin
if(exists(select conflictId from
(select (select top 1 t.Id from dbo.Tasks t
where t.UserId = i.UserId
and o.[Status] != N'Done'
and o.Id != i.Id) as conflictId
from inserted i
where i.UserId is not null) conflicts
where conflictId is not null))
begin
raiserror ('Concurrent user assignment detected.', 16, 1);
rollback;
end
end
If I create lots of assignments in parallel (regularly > 10) then some of them will be rejected by the constraint/trigger, other will be able to concurrently save UserId in the database. As a result by database data will be inconsistent.
I've verified both approaches in Management Studio and it prevents me from corrupting my data. I'm unable to assign multiple 'Active' tasks to a given user.
What is improtant to say, I'm using Entity Framework 6.x
at my Backend to save my data (SaveChangesAsync
) and every save action is executed in a separate transaction with default Transaction Isolation level ReadCommited
What could be wrong in my approaches and how to keep my data consistent?