You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Entity Framework Core中通过SQL层面验证记录能否插入

解决座位预订的并发原子性问题

原代码的核心问题是两次独立的数据库调用之间存在竞态条件——检查座位可用后,到执行插入前的这段时间里,其他请求可能已经抢走了这个座位,导致重复预订。

针对你的复杂业务场景,以下是几种具备原子性的SQL方案,确保判断和插入操作在数据库层面一次性完成,彻底避免并发冲突:

方案1:IF NOT EXISTS + INSERT 原子操作

修正你提供的示例SQL(原示例存在语法错误),实现判断与插入的原子执行:

DECLARE @IsSuccess BIT = 0;

-- 原子性检查:只有座位未被占用时才执行插入
IF NOT EXISTS (SELECT 1 FROM Tickets WHERE SeatNumber = @SeatNumber)
BEGIN
    INSERT INTO Tickets (UserId, SeatNumber)
    VALUES (@UserId, @SeatNumber);
    SET @IsSuccess = 1;
END;

-- 返回操作结果:1表示预订成功,0表示座位已被占用
SELECT @IsSuccess AS Result;

在EF Core中调用该SQL的示例代码:

public bool BookSeat(int userId, string seatNumber)
{
    var paramUserId = new SqlParameter("@UserId", userId);
    var paramSeatNumber = new SqlParameter("@SeatNumber", seatNumber);
    var paramResult = new SqlParameter("@IsSuccess", SqlDbType.Bit) 
    { 
        Direction = ParameterDirection.Output 
    };

    _dbContext.Database.ExecuteSqlRaw(@"
        DECLARE @IsSuccess BIT = 0;

        IF NOT EXISTS (SELECT 1 FROM Tickets WHERE SeatNumber = @SeatNumber)
        BEGIN
            INSERT INTO Tickets (UserId, SeatNumber)
            VALUES (@UserId, @SeatNumber);
            SET @IsSuccess = 1;
        END;

        SELECT @IsSuccess AS Result;", paramUserId, paramSeatNumber, paramResult);

    return (bool)paramResult.Value;
}

方案2:INSERT ... SELECT 简化写法

这种方式将判断逻辑直接嵌入INSERT语句,语法更简洁,原子性同样有保障:

-- 仅当座位未被占用时,才插入新记录
INSERT INTO Tickets (UserId, SeatNumber)
SELECT @UserId, @SeatNumber
WHERE NOT EXISTS (SELECT 1 FROM Tickets WHERE SeatNumber = @SeatNumber);

-- 通过返回插入行数判断结果:1=成功,0=失败
SELECT @@ROWCOUNT AS Result;

EF Core中的调用示例:

public bool BookSeat(int userId, string seatNumber)
{
    var paramUserId = new SqlParameter("@UserId", userId);
    var paramSeatNumber = new SqlParameter("@SeatNumber", seatNumber);

    var rowsAffected = _dbContext.Database.ExecuteSqlRaw(@"
        INSERT INTO Tickets (UserId, SeatNumber)
        SELECT @UserId, @SeatNumber
        WHERE NOT EXISTS (SELECT 1 FROM Tickets WHERE SeatNumber = @SeatNumber);

        SELECT @@ROWCOUNT;", paramUserId, paramSeatNumber);

    return rowsAffected > 0;
}

复杂业务场景扩展

如果你的校验逻辑不止座位占用(比如用户权限、场次有效性等),只需将所有校验条件加到WHERE子句中即可,整个操作依然保持原子性:

INSERT INTO Tickets (UserId, SeatNumber, SessionId)
SELECT @UserId, @SeatNumber, @SessionId
WHERE NOT EXISTS (SELECT 1 FROM Tickets WHERE SeatNumber = @SeatNumber AND SessionId = @SessionId)
AND EXISTS (SELECT 1 FROM Sessions WHERE Id = @SessionId AND StartTime > GETDATE()) -- 校验场次未开始
AND EXISTS (SELECT 1 FROM Users WHERE Id = @UserId AND IsActive = 1); -- 校验用户状态正常

内容的提问来源于stack exchange,提问作者I Bowyer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 21:15:36