如何进行完全同步的多步骤数据库操作?

时间:2017-11-25 02:57:04

标签: asp.net sql-server asp.net-mvc asp.net-mvc-5

当前项目:

  • DotNet 4.7
  • MVC 5
  • C#7.1
  • 使用Linq Lambda进行CRUD操作的存储库模式

关于可能看起来像并发问题的问题我有点问题,但实际上并非如此。

您看,我正在构建的系统允许用户注册课程。每个班级都有一定的能力,并且需要能够让人们同时处于登记和候补状态。

注册表通过具有注册布尔值来实现此目的,对于已登记为真,对于等待列表为假。问题是,在注册过程中,我需要一个查询计数,它会显示现有注册用户的数量,通过比较Enrolled = yes计数和类的容量值来查看仍有多少个开放点(不同的表),如果有任何空位,允许用户注册,注册标志设置为true。如果没有剩余点,则注册用户,注册标志设置为False。

当剩下一个点并且两个用户同时注册(或者足够接近以使系统与另一个同时处理一个操作时)存在问题。我在现有的注册表中看到了时间戳,有时候两个用户在输入数据时非常非常接近。

我需要一个在SQL或MVC中的系统,它一次只能进行一次用户注册。在完成第一个用户注册的保存之前,它将开始第二次用户注册(查看注册表中有多少已注册=是),这将没有任何机会在任何情况下,注册数量=是的注册数量都不会超过用户注册的类别的容量。

换句话说,实际的query-count-compare-decision-record进程(数据库的至少两次触摸,一次查询,一次记录)需要绝对同步,并且基本上阻止所有其他注册尝试,直到这个过程完成了。因为这将在一个代码块中完成,所以我可以肯定地说,表永远不会被“锁定”,而不是实际运行代码所需的时间。这个过程是在所有相关的用户交互之后。但由于这是一个读取(计数所有当前注册,其中Enrolled = yes,以确定当前写入是否需要注册=是或=否)然后写入,我担心在读取和读取之间发生第二次读取写第一个,第二个写在第一个之后,将Enrolled = true计数保持在不合规状态。

由于注册的用户数量只会很小(ish),我并不过分关注性能,但我对如何实际实现这一点感到困惑。

建议?

1 个答案:

答案 0 :(得分:2)

IMO,最重要的是,至少在数据库中实现验证。不要让数据违反这样的规则。

我认为你应该使用插入/更新触发器并在违反约束时阻止声明。它与实施检查约束相同,但是如果您在课程中超过学生限制,则可以灵活地检查课程容量,计算班级中的学生数量,并抛出错误(回滚交易)。

触发器在与触发它的DML语句相同的事务中执行,因此如果您发生错误,则将其全部回滚。这样的事情应该有效:

CREATE TABLE class (class_id INT, capacity INT)
GO

CREATE TABLE registration (class_id INT, student_id INT, enrolled BIT)
GO

CREATE TRIGGER i_registration
ON registration
FOR INSERT, UPDATE
AS
BEGIN
    SET NOCOUNT ON;

    IF EXISTS (
        SELECT * FROM (
            SELECT SUM(CASE WHEN enrolled = 1 THEN 1 ELSE 0 END) OVER (PARTITION BY r.class_id) enrolled, c.capacity
            FROM registration r
            INNER JOIN class c ON r.class_id = c.class_id
        )sq WHERE enrolled > capacity
    )
        THROW 51000, 'Class is full!', 1
END
GO

一些DML语句示例:

insert into class values (1, 5)

insert into registration values (1, 1, 1)
insert into registration values (1, 2, 1)
insert into registration values (1, 3, 0)
insert into registration values (1, 4, 0)

insert into registration values (1, 5, 1)
insert into registration values (1, 6, 1)
insert into registration values (1, 7, 1)
insert into registration values (1, 8, 1) -- blocked!   

update registration set enrolled = 1 where student_id = 3 -- blocked!

当您在客户端应用程序中调用SaveChangesAsync()时,它将引发异常(System.Data.Entity.Infrastructure.DbUpdateException),您将能够看到您的异常消息&数字通过级联内部异常,直到找到System.Data.SqlClient.SqlException。您可以使用它们来确定如何向用户显示错误消息:

Exception Info