为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C#
精慕HU 2022-11-13 13:46:40
假设我有一个执行以下操作的控制器操作:检查在特定时间是否有日历槽检查是否没有已预订的约会与该时段重叠如果两个条件都满足,它会在给定时间创建一个新约会简单的实现会带来多个问题:如果在第 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);    }}始终防止并发问题的最佳方法是什么,我应该如何处理最终的死锁?
查看完整描述

3 回答

?
明月笑刀无情

TA贡献1828条经验 获得超4个赞

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

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

如果在第 3 步之前删除了 1 中获取的日历槽怎么办?

数据库会抛出外键违规异常

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

DB 会抛出重复键异常

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

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


查看完整回答
反对 回复 2022-11-13
?
智慧大石

TA贡献1946条经验 获得超3个赞

看来您需要一种悲观的并发方法来管理您的任务。不幸的是,它在 Entity Framework Core 中不受支持。


或者,您可以使用静态 ConcurrentDictionary 或实现您自己的 ConcurrentHashSet 以防止多个请求并避免在第 2 步之后但在第 3 步之前预订另一个约会。


关于在步骤 3 问题之前删除 1 中获取的日历槽,我认为在 Appointment 和 Slot 之间有一个外键关系来检查 SaveChanges 处的数据库完整性,或者让 ConcurrentDictionary/ConcurrentHashSet Public 并从另一个操作中检查它(删除 Slots,等)在执行它们之前,是解决它的好选择。


static ConcurrentDictionary<int, object> operations = new ConcurrentDictionary<int, object>();


    public async Task<IActionResult> AControllerAction()

    {

        int? calendarSlotId = 1; //await dbContext.Slots.FirstOrDefaultAsync(..., cancellationToken))?.Id;


        try

        {

            if (calendarSlotId != null && operations.TryAdd(calendarSlotId.Value, null))

            {

                bool appointmentsAreOverlapping = false; //await dbContext.Slots.Where(...).AnyAsync(cancellationToken);


                if (!appointmentsAreOverlapping)

                {

                    //dbContext.Appointments.Add(...);

                    //await dbContext.SaveChangesAsync(cancellationToken);


                    return ...; //All done!

                }


                return ...; //Appointments are overlapping

            }


            return ...; //There is no slot or slot is being used

        }

        catch (Exception ex)

        {

            return ...; //ex exception (DB exceptions, etc)

        }

        finally

        {

            if (calendarSlotId != null)

            {

                operations.TryRemove(calendarSlotId.Value, out object obj);

            }

        }

    }


查看完整回答
反对 回复 2022-11-13
?
Cats萌萌

TA贡献1805条经验 获得超9个赞

有时,在高可用性场景中,建议权衡即时一致性(通过事务获得)以换取最终一致性(通过工作流/传奇获得)。


在您的示例中,您可以考虑一种方法,该方法使用中间状态来存储“待定”约会,然后对其一致性进行新的检查。


public async Task Fn(..., CancellationToken cancellationToken)

{

    // suppose "appointment" is our entity, we will store it as "pending" using

    // PendingUntil property (which is Nullable<DateTimeOffset>).

    // an appointment is in "pending" state if the PendingUntil property is set

    // (not null), and its value is >= UtcNow

    var utcNow = DateTimeOffset.UtcNow;

    appointment.PendingUntil = utcNow.AddSeconds(5);


    // we will then use this property to find out if there are other pending appointments


    var calendarSlotExists = await dbContext.Slots.Where(...).AnyAsync(cancellationToken);

    var appointmentsAreOverlapping = await dbContext.Appointments

                                                    .Where(...)

                                                    .Where(a => a.PendingUntil == null || 

                                                                a.PendingUntil >= now)

                                                    .AnyAsync(cancellationToken);


    if (calendarSlotExists && !appointmentsAreOverlapping)

        dbContext.Appointments.Add(appointment);

    else

        return BadRequest(); // whatever you what to return


    await dbContext.SaveChangesAsync(cancellationToken); // save the pending appointment


    // now check if the pending appointment is still valid


    var calendarSlotStillExists = await dbContext.Slots.Where(...).AnyAsync(cancellationToken); // same query as before


    // a note on the calendar slot existance: you should of course negate any

    // slot deletion for (pending or not) appointments.


    // we will then check if there is any other appointment in pending state that was

    // stored inside the database "before" this one.

    // this query is up to you, below you'll find just an example


    var overlappingAppointments = await dbContext.Appointments.Where(...)

                                                 .Where(a => a.Id != appointment.Id &&

                                                             a.PendingUntil == null || 

                                                             a.PendingUntil >= now)

                                                 .ToListAsync(cancellationToken);


    // we are checking if other appointments (pending or not) have been written to the DB

    // of course we need to exclude the appointment we just added


    if (!calendarSlotStillExists || overlappingAppointments.Any(a => a.PendingUntil == null || a.PendingUntil < appointment.PendingUntil)

    {

        // concurrency check failed

        // this means that another appointment was added after our first check, but before our appointment.

        // we have to remove the our appointment

        dbContext.Appointments.Remove(appointment);

        await dbContext.SaveChangesAsync(cancellationToken); // restore DB

        return BadRequest(); // same response as before

    }


    // ok, we can remove the pending state

    appointment.PendingUntil = null;


    await dbContext.SaveChangesAsync(cancellationToken); // insert completed

    return Ok();

}

当然,这会对数据库造成双重打击,但会完全避免事务(带有死锁和锁定延迟)。


您只需要评估哪个方面对您更重要:可扩展性或即时一致性。


查看完整回答
反对 回复 2022-11-13
  • 3 回答
  • 0 关注
  • 202 浏览

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号