3 回答

TA貢獻1828條經(jīng)驗 獲得超4個贊
數(shù)據(jù)庫完整性檢查是您最好的朋友
根據(jù)您的描述,您的約會是基于時段的。這使問題變得簡單得多,因為您可以有效地為表定義唯一SlotId
約束Appointments
。然后你需要一個外鍵作為Appointments.SlotId
參考Slot.Id
如果在第 3 步之前刪除了 1 中獲取的日歷槽怎么辦?
數(shù)據(jù)庫會拋出外鍵違規(guī)異常
如果在第 2 步之后但第 3 步之前預約了另一個約會怎么辦?
DB 會拋出重復鍵異常
接下來您需要做的是捕捉這兩個異常并將用戶重定向回預訂頁面。再次從數(shù)據(jù)庫重新加載數(shù)據(jù)并檢查任何無效條目,通知用戶進行修改并重試。
對于死鎖部分,它實際上取決于您的表結構。訪問數(shù)據(jù)的方式、索引它們的方式以及數(shù)據(jù)庫的查詢計劃。對此沒有確定的答案。

TA貢獻1946條經(jīng)驗 獲得超3個贊
看來您需要一種悲觀的并發(fā)方法來管理您的任務。不幸的是,它在 Entity Framework Core 中不受支持。
或者,您可以使用靜態(tài) ConcurrentDictionary 或?qū)崿F(xiàn)您自己的 ConcurrentHashSet 以防止多個請求并避免在第 2 步之后但在第 3 步之前預訂另一個約會。
關于在步驟 3 問題之前刪除 1 中獲取的日歷槽,我認為在 Appointment 和 Slot 之間有一個外鍵關系來檢查 SaveChanges 處的數(shù)據(jù)庫完整性,或者讓 ConcurrentDictionary/ConcurrentHashSet Public 并從另一個操作中檢查它(刪除 Slots,等)在執(zhí)行它們之前,是解決它的好選擇。
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);
}
}
}

TA貢獻1805條經(jīng)驗 獲得超9個贊
有時,在高可用性場景中,建議權衡即時一致性(通過事務獲得)以換取最終一致性(通過工作流/傳奇獲得)。
在您的示例中,您可以考慮一種方法,該方法使用中間狀態(tài)來存儲“待定”約會,然后對其一致性進行新的檢查。
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();
}
當然,這會對數(shù)據(jù)庫造成雙重打擊,但會完全避免事務(帶有死鎖和鎖定延遲)。
您只需要評估哪個方面對您更重要:可擴展性或即時一致性。
- 3 回答
- 0 關注
- 108 瀏覽
添加回答
舉報