Альтернативные способы обращения с валютой в системе кинотеатра
Моя группа и я создали систему бронирования в кинотеатре для школьного проекта, где предметом был контроль над валютой. Это сделано с помощью C# и Entity Framework в 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();
}
}
С помощью этого метода экран (кинозал) строится с местами в качестве флажков, где вы можете выбрать несколько сидячих мест одновременно. Затем выбранные вами места будут отправлены с помощью bookingId посредством вызова AJAX в метод 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;
}
Как вы можете заметить, мы использовали ReadCommited изоляцию уровня, которая, как мы предполагаем, является пессимистичной блокировкой и будет гарантировать отсутствие конфликтов при добавлении мест в ReservationSeats.
И я предполагаю, что мы не встретим никаких тупиков, поскольку мы блокируем только одну таблицу.
Я понимаю, что одним из способов работы оптимистичного конкуррента является то, что перед тем как вы обновите базу данных, вы проверите, была ли база данных изменена с момента извлечения записи. Могли бы мы до того, как добавить места (и bookingId) в таблицу bookingSeats, проверить, были ли они зарезервированы для шоу.