在实体框架核心中执行业务规则

Sho*_*hoe 7 .net c# transactions entity-framework-core .net-core

假设我有一个执行以下操作的控制器操作:

  1. 检查在特定时间是否有日历槽
  2. 检查是否有已预约的约会与该时段重叠
  3. 如果同时满足两个条件,它将在给定时间创建一个新约会

简单的实现存在多个问题:

  • 如果在步骤3之前删除了在1中提取的日历槽怎么办?
  • 如果在步骤2之后但在步骤3之前又预定了另一个约会怎么办?

解决这些问题的方法似乎是使用SERIALIZABLE事务隔离级别。问题在于,每个人似乎都认为此事务隔离级别非常危险,因为它可能导致死锁。

给出以下简单的解决方案:

public class AController
{
    // ...
    public async Task Fn(..., CancellationToken cancellationToken)
    {
        var calendarSlotExists = dbContext.Slots.Where(...).AnyAsync(cancellationToken);
        var appointmentsAreOverlapping = dbContext.Appointments.Where(...).AnyAsync(cancellationToken);
        if (calendarSlotExists && !appointmentsAreOverlapping)
            dbContext.Appointments.Add(...);
        dbContext.SaveChangesAsync(cancellationToken);
    }
}
Run Code Online (Sandbox Code Playgroud)

始终防止并发问题的最佳方法是什么?我应该如何处理最终的死锁?

Ste*_*eve 4

数据库完整性检查是您最好的朋友

根据您的描述,您的预约是基于时段的。这使得问题变得更加简单,因为您可以有效地为表定义唯一SlotId约束Appointments。然后你需要一个外键作为Appointments.SlotId参考Slot.Id

如果在步骤 3 之前删除在 1 中获取的日历槽会怎样?

DB会抛出外键违规异常

如果在步骤 2 之后但在步骤 3 之前预订了另一个预约怎么办?

DB会抛出重复键异常

接下来您需要做的是捕获这两个异常并将用户重定向回预订页面。再次从数据库重新加载数据并检查是否有无效条目,通知用户进行修改并重试。

对于死锁部分,它实际上取决于您的表结构。访问数据的方式、索引数据的方式以及数据库的查询计划。对此没有明确的答案。