1

我和我的团队为一个学校项目制作了一个电影院预订系统,该项目的主题是货币控制。它是用 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 表之前检查它们是否已被保留用于演出。

4

0 回答 0