私のグループと私は、主題が通貨制御である学校のプロジェクトのために映画予約システムを作成しました。これは、プレゼンテーション層が mvc プロジェクトで構成されている n 層アーキテクチャの c# およびエンティティ フレームワークで作成されています。
「予約する座席を選択する」段階で悲観的ロック (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();
}
}
このメソッドから、複数の座席を一度に選択できるチェックボックスとして、画面 (シネマ ホール) が構成されます。選択した座席は、TryBookSeats メソッドへの AJAX 呼び出しを介して、reservationId と共に送信されます。このメソッドは、最初に誰かがあなたの座席を予約しようとしたかどうかを確認し、そうでない場合は、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 にシートを追加する際に競合が発生しないようにします。
また、ロックするテーブルは 1 つだけなので、デッドロックは発生しないと思います。
オプティミスティック カレントが機能する 1 つの方法は、データベースの更新をコミットする前に、レコードを取得してからデータベースが変更されていないかどうかを確認することだと理解しています。座席(およびreservationId)をreservationSeatsテーブルに追加する前に、それらがショーのために予約されているかどうかを確認することを選択できたでしょうか。
