У меня есть эти две сущности:
public class Ticket
{
public int Id { get; set; }
public int ScheduleId { get; set; }
public int SeatId { get; set; }
[DataType(DataType.Date)]
[Column(TypeName = "Date")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime ForDate { get; set; }
public Seat Seat { get; set; }
public Schedule Schedule { get; set; }
public ICollection<TicketStop> TicketStops { get; set; }
}
public class TicketStop
{
public int Id { get; set; }
public int TicketId { get; set; }
public int LineStopStationId { get; set; }
public Ticket Ticket { get; set; }
public LineStopStation LineStopStation { get; set; }
}
public class LineStopStation
{
public int Id { get; set; }
public int LineId { get; set; }
public int StopId { get; set; }
public int Order { get; set; }
public bool IsLastStop { get; set; }
public Line Line { get; set; }
public Stop Stop { get; set; }
}
Бизнес-кейс заключается в том, что я внедряю систему бронирования билетов на автобусы (в основном для целей обучения) и хочу найти перекрывающиеся билеты.
Комбинация LineId + ScheduleId + ForDate однозначно определяет, что билет предназначен для определенного автобуса в определенную дату и время отправления.
Проблема, с которой я сталкиваюсь, состоит в том, чтобы определить, учитывая начальное и конечное местоположение, перекрываются ли два билета для одной или нескольких остановок.
Сущность LineStopStation содержит информацию о StopId и Order, в которых она посещается во время автобусной поездки. Таким образом, перекрывающиеся билеты в какой-то момент будут иметь одинаковый номер Order (если только это не последняя остановка, что означает, что пассажир выходит из автобуса).
Итак, у меня есть LineId, ScheduleId, StartId и EndId, где starId и endId соответствуют LineStopStation.StopId, поэтому в конечном итоге я могу получить из них Орден вот так.
int startStationOrder = _context.LineStopStations
.First(l => l.LineId == lineId && l.StopId == startId).Order;
int endStationOrder = _context.LineStopStations
.First(l => l.LineId == lineId && l.StopId == endId).Order;
Поэтому я вполне убежден, что, имея всю эту информацию, я смогу найти, есть ли в таблице TicketStop билет, который совпадает с рассматриваемыми данными. TicketStop работает так - если кто-то купил билет на 3 остановки у меня там будет три записи с одинаковым TicketId и три разных LineStopStationId.
Я чувствую, что этот вопрос стал больше, чем нужно. Итак, в основном у меня есть это:
public List<Seat> GetAvailableSeats(int lineId, int scheduleId, int startId, int endId, DateTime forDate)
{
int startStationOrder = _context.LineStopStations
.First(l => l.LineId == lineId && l.StopId == startId).Order;
int endStationOrder = _context.LineStopStations
.First(l => l.LineId == lineId && l.StopId == endId).Order;
var reservedSeats = _context.TicketStops
.Where(t => t.Ticket.ScheduleId == scheduleId)
.Where(t => t.Ticket.ForDate == forDate)
//basically I don't know how to proceed here.
//In pseudo code it should be something like:
.Where(t => t.Min(Order) >= endStationOrder || t.Max(Order) <= startStationOrder
}
Но обс. это не так LINQ работает. Итак, как я могу найти все билеты, в которых этот диапазон пересекается?





Без глубокого анализа вашей модели, возможно, что-то подобное могло бы дать вам представление?
var reservedSeats = _context.TicketStops
.GroupBy(t => new { t.Ticket.ScheduleId, t.Ticket.ForDate })
.Where(tg => tg.Key == new { ScheduleId = scheduleId, ForDate = forDate })
.Where(tg => tg.Min(t => t.LineStopStation.Order) >= endStationOrder || tg.Max(t => t.LineStopStation.Order) <= startStationOrder);
Вы также можете сначала отфильтровать и сделать пустой GroupBy:
var reservedSeats = _context.TicketStops
.Where(t => t.Ticket.ScheduleId == scheduleId && t.Ticket.ForDate == forDate)
.GroupBy(t => 1)
.Where(tg => tg.Min(t => t.LineStopStation.Order) >= endStationOrder || tg.Max(t => t.LineStopStation.Order) <= startStationOrder);
Чтобы вернуть все SeatId, вам просто нужно выбрать их из группы.
var reservedSeats = _context.TicketStops
.Where(t => t.Ticket.ScheduleId == scheduleId && t.Ticket.ForDate == forDate)
.GroupBy(t => 1)
.Where(tg => tg.Min(t => t.LineStopStation.Order) >= endStationOrder || tg.Max(t => t.LineStopStation.Order) <= startStationOrder);
.SelectMany(tg => tg.Select(t => t.Ticket.SeatId));
Похоже, у вас будет несколько билетов в группе tg — будут ли все SeatId одинаковыми, или вам нужно принять решение, какой из них вернуться? Это, безусловно, сработает, но будет возвращаться только первое совпадение в (возможно) случайном порядке.
Да, я понял это. SeatId разные, поэтому получить только один недостаточно, но подход GroupBy — это то, что нужно, ИМО. Однако, поскольку это одна из самых важных частей в моей реализации, я решил также включить min/max Order в Ticket Entity, что денормализует структуру, но делает ее намного проще в использовании. Так что пока я не буду прилагать к этому больше усилий. Если вам интересно, я буду признателен за решение с GroupBy, где возвращаются все SeatId.
@Leron Это просто - вам просто нужно выбрать их из каждой группы. Я использовал SelectMany, чтобы отменить группу, но вы также можете сделать вложенную Select, а затем First(). LINQ to Entities может преобразовывать одно в другое.
Это было то, что я искал. Кажется, я сам разобрался, но если у вас есть время, еще один вопрос. После группировки и фильтрации я хочу взять фактические
SeatId, которые следуют в этих условиях. Правильно ли написать:.Select(tg => tg.First().Ticket.SeatId)Вроде возвращает правильный результат, но, как видите, я не владею LINQ\GroupBy. В любом случае, спасибо.