2016-04-23 102 views
4

我有一个Apartments的列表,其中每个Apartment有它的ApartmentRooms,其中每个ApartmentRoom都有它的DateBatches。每个DateBatch都有Dates的列表,表示占用日期(每个拍摄日期一个记录)。实体框架日期组合查询

Apartment 1 
Room 1 
    DateBatch 
     1.5.2015 
     2.5.2015 
    DateBatch 2 
     8.5.2015 
     9.5.2015 
Room 2 
    DateBatch 
     5.5.2015 
     6.5.2015 

通过这种方式,你可以看到,这间公寓有2间房间,其中1间是采取下列日期1.5,2.5,8.5,9.5和2室被占用的5.5和6.5。

用户可以输入想要停留的N天和X连续天数的期望时间段。

因此,例如,用户输入1.5到15.5的时间段,他想要睡10个晚上,我需要列出至少有一个公寓房间可用于任何可能的日期组合的所有公寓,这将是下面在这种情况下:

1.5-10.5 
2.5-11.5 
3.5-12.5 
4.5-13.5 
5.5-14.5 

到目前为止,我已经尝试了这一点,它仅适用于第一次的foreach迭代,因为和标准,没有或标准,我的foreach会连接查询觉得这是一个非常不好的做法走。

public static IQueryable<Apartment> QueryByPeriod(this IQueryable<Apartment> apartments, DateTime PeriodStart, DateTime PeriodEnd, int StayDuration) 
{ 
     var possibleDateRanges = new List<List<DateTime>>(); 

     //set all possible start dates for the desired period 
     for (var i = PeriodStart; i <= PeriodEnd.AddDays(-StayDuration); i = i.AddDays(1)) 
     { 
      List<DateTime> dates = new List<DateTime>(); 

      foreach(var date in i.DateRange(i.AddDays(StayDuration-1))) 
      { 
       dates.Add(date); 
      } 

      possibleDateRanges.Add(dates); 
     } 

     //filter by date range 
     //select apartment rooms where one of possible combinations is suitable for selected period 
     foreach (var possibleDates in possibleDateRanges) 
     { 
      apartments = apartments.Where(m => m.ApartmentRooms 
      .Any(g => g.OccupiedDatesBatches.Select(ob => ob.OccupiedDates).Any(od => od.Any(f => possibleDates.Contains(f.Date))) 
      ) == false); 
     } 

     return apartments; 
    } 

有什么建议吗?

+1

我不会尝试用SQL语句来做任何事情到数据库。我会做一些与SQL的代码,其余的与C#。我首先会查询数据库中的开始日期和结束日期之间占用的房间,这将是用户想要房间的最早和最晚日期。然后使用C#代码来查找哪些房间可用于需要的时间段。 – jdweng

+0

对于房间/日期批次,“Activity.Active”意味着什么? –

+0

如果它从系统中删除,它的活动将被标记为活动= false。 我会从查询中删除它以避免混淆。 – Robert

回答

1

这里是一个纯粹的(不需要外部包)LINQ to Entities解决方案。

开始确定可能的开始日期列表:

var startDates = Enumerable.Range(0, PeriodEnd.Subtract(PeriodStart).Days - StayDuration + 1) 
    .Select(offset => PeriodStart.AddDays(offset)) 
    .ToList(); 

然后使用以下查询:

var availableApartments = apartments.Where(a => a.ApartmentRooms.Any(ar => 
    startDates.Any(startDate => !ar.OccupiedDatesBatches.Any(odb => 
     odb.OccupiedDates.Any(od => 
      od.Date >= startDate && od.Date < DbFunctions.AddDays(startDate, StayDuration)))))); 

这个方案的好处是,它可以很容易地进行扩展。上述查询返回可用的公寓,但不提供哪些空间可用以及何时可能需要提供给用户的信息。使用上面的方法,你可以得到这样的信息:

public class AvailableApartmentInfo 
{ 
    public Apartment Apartment { get; set; } 
    public Room Room { get; set; } 
    public DateTime StartDate { get; set; } 
} 

var availableApartmentInfo = 
    from a in apartments 
    from ar in a.ApartmentRooms 
    from startDate in startDates 
    where !ar.OccupiedDatesBatches.Any(odb => 
     odb.OccupiedDates.Any(od => 
      od.Date >= startDate && od.Date < DbFunctions.AddDays(startDate, StayDuration))) 
    select new AvailableApartmentInfo { Apartment = a, Room = ar, StartDate = startDate }; 
+0

当日期范围较高并且涉及更多日期组合时,此答案优于以前的答案。谢谢。我的猜测是,从前面的答案'Any'击败所有''功能? – Robert

+0

实际上'任何'=='!全部',反之亦然。 http://stackoverflow.com/questions/36482467/entity-framework-where-clausule-with-any-and-all-use/36484562#36484562 –

+0

我明白了,但在上面的答案中,我们有=='所有' ,所以我猜这就是你的查询工作更快的原因 – Robert

2

要将多个条件与OR组合,您可以使用(例如)LinqKit库。通过安装的NuGet,添加using LinqKit;然后:

public static IQueryable<Apartment> QueryByPeriod(this IQueryable<Apartment> apartments, DateTime PeriodStart, DateTime PeriodEnd, int StayDuration) { 
     var possibleDateRanges = new List<Tuple<DateTime, DateTime>>(); 
     // list all ranges, so for your example that would be: 
     //1.5-10.5 
     //2.5-11.5 
     //3.5-12.5 
     //4.5-13.5 
     //5.5-14.5    
     var startDate = PeriodStart; 
     while (startDate.AddDays(StayDuration - 1) < PeriodEnd) { 
      possibleDateRanges.Add(new Tuple<DateTime, DateTime>(startDate, startDate.AddDays(StayDuration - 1))); 
      startDate = startDate.AddDays(1); 
     } 
     Expression<Func<Apartment, bool>> condition = null; 
     foreach (var range in possibleDateRanges) {     
      Expression<Func<Apartment, bool>> rangeCondition = m => m.ApartmentRooms  
       // find rooms where ALL occupied dates are outside target interval    
       .Any(g => g.OccupiedDatesBatches.SelectMany(ob => ob.OccupiedDates).All(f => f.Date < range.Item1 || f.Date > range.Item2) 
       ); 
      // concatenate with OR if necessary 
      if (condition == null) 
       condition = rangeCondition; 
      else 
       condition = condition.Or(rangeCondition); 
     } 
     if (condition == null) 
      return apartments; 
     // note AsExpandable here 
     return apartments.AsExpandable().Where(condition); 
    } 

请注意,我还修改了你的逻辑。当然,这个逻辑是单元测试的最佳人选,如果您正在开发一个严肃的项目 - 您应该定义使用内存中的EF提供程序(或模拟)对不同的条件进行测试。

+0

我认为可能的日期范围期限存在一些缺陷。它以14.5-18.5结尾,因为它是最后一条记录, – Robert

+0

Nvm:发现问题。它应该是'i <=(PeriodEnd - PeriodStart)。TotalDays - StayDuration'。让我测试一下,如果一切正常,我会将你的答案标记为完整:) – Robert

+0

事实上,我误解了你如何使用范围。我认为用户设置_start_期间的范围(即从1到5开始,停留10天)。修改我的答案。 – Evk