我和我的团队为一个学校项目制作了一个电影院预订系统,该项目的主题是货币控制。它是用 c# 和实体框架在一个 n 层架构中制作的,其中表示层由一个 mvc 项目组成。
我们选择在“选择座位进行预订”阶段使用悲观锁定 (IsolationLevel.ReadCommited),以便在有人检查座位是否可用以及将座位添加到预订时锁定数据库。我目前正在寻找例如乐观并发是否可能是一个选项,如果是的话,它将如何工作。
当你点击一个特定的节目时,它会自动为你创建一个空的预订,然后用座位和信息构建屏幕(电影院),无论它们是否可用。
以下是创建链接到屏幕(电影院)的座位列表的方法:
public static List<SeatReservationInfo> GetSeatInfoForShow(Guid reservationId)
{
using (EntityContext db = new EntityContext())
{
//Retrieve a reservation on its id
var reservation = db.Reservations.FirstOrDefault(r => r.Id == reservationId);
//Retrieve the show linked to the reservation
var show = db.Shows.First(i => i.Id == reservation.ShowId);
//Used to check if the reservation is expired
var expired = DateTime.Now.Subtract(new TimeSpan(0, 0, 15, 0));
//Henter sæder ud tilhørende en specifik sal og laver Seat modellen om til en SeatReservationInfo model
//Retrieve all seats linked to a specific screen and turn the Seat model into a SeatReservationModel which containt availablity status of the seat
return
db.Seats.Where(s => s.ScreenId == show.ScreenId)
.OrderBy(s => s.RowNumber)
.ThenBy(s => s.SeatNumber)
.Select(s => new SeatReservationInfo
{
Id = s.Id,
Type = s.Type,
RowNumber = s.RowNumber,
SeatNumber = s.SeatNumber,
Availability = s.ReservationSeats.Any(a => a.ReservationId == reservationId)
? SeatAvailability.ReservedSelf
: s.ReservationSeats.Any(
a =>
a.Reservation.ShowId == reservation.ShowId &&
(a.Reservation.Status == ReservationStatus.Completed ||
(a.Reservation.Status == ReservationStatus.Started &&
DateTime.Compare(a.Reservation.Created, expired) >= 0)))
? SeatAvailability.Reserved
: SeatAvailability.Available
}).ToList();
}
}
通过这种方法,屏幕(电影院)是用座位作为复选框构建的,您可以在其中一次选择多个座位。然后通过 AJAX 调用将您选择的座位与您的 reservationId 一起发送到 TryBookSeats 方法,该方法将首先查看是否有人尝试预订您的座位,如果没有,则通过将它们放置在 ReservationSeats 表中进行预订。
这是 TryBookSeats 方法:
public static bool TryBookSeats(List<Guid> seatIds, Guid reservationId)
{
//bool who will become true if seats are available
bool success;
//Starts a database connection
using (var db = new EntityContext())
//Starts a transaction where the isolationlevel is set to ReadCommitted (pessimistic concurrency)
using (var scope = db.Database.BeginTransaction(IsolationLevel.ReadCommitted))
{
//Used to check if the reservation is expired
var expired = DateTime.Now.Subtract(new TimeSpan(0, 0, 15, 0));
//Retrieve a reservation that hasnt expired
var reservation = db.Reservations.First(x => x.Id == reservationId && DateTime.Compare(x.Created, expired) >= 0);
//Checks if the selected seats are available. if so set success to true
success = !db.ReservationSeats.Any(
i =>
i.ReservationId != reservationId && i.Reservation.ShowId == reservation.ShowId &&
seatIds.Contains(i.SeatId) &&
(i.Reservation.Status == ReservationStatus.Completed ||
(i.Reservation.Status == ReservationStatus.Started &&
DateTime.Compare(i.Reservation.Created, expired) >= 0)));
if (success)
{
//Remove last selected seats connected to the current reservation
db.ReservationSeats.RemoveRange(db.ReservationSeats.Where(i => i.ReservationId == reservationId));
//Add seats to the database
foreach (var id in seatIds)
{
db.ReservationSeats.Add(new ReservationSeat
{
Id = Guid.NewGuid(),
ReservationId = reservationId,
SeatId = id
});
}
}
db.SaveChanges();
scope.Commit();
}
return success;
}
如您所见,我们使用了isolationLevel ReadCommited,我们认为它是悲观锁定,并将确保在将席位添加到ReservationSeats 时不会发生冲突。
而且我认为我们不会遇到任何死锁,因为我们只锁定了一张表。
我知道乐观并发的工作方式之一是在您提交数据库更新之前,您将检查数据库在您检索记录后是否已更改。我们是否可以选择,在将座位(和 reservationId)添加到 reservationSeats 表之前检查它们是否已被保留用于演出。