【问题标题】:Entity Framework date combination query实体框架日期组合查询
【发布时间】:2016-04-23 06:53:13
【问题描述】:

我有一个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 将查询与 AND 条件连接,而不是 OR 条件,我认为这是一个非常糟糕的方法。

 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;
    }

有什么建议吗?

【问题讨论】:

  • 我不会尝试使用 sql 语句对数据库执行所有操作。我会用 sql 做一些代码,用 c# 做其余的代码。我将首先查询数据库以查找在开始日期和结束日期之间占用的房间,这将是用户想要房间范围内的最早和最晚日期。然后使用 c# 代码查找在所需天数内可用的房间。
  • Activity.Active 对房间/日期批次意味着什么?
  • 如果它是从系统中删除的东西,它的活动将被标记为 Active = false。我将从查询中删除它以避免混淆。

标签: c# sql-server entity-framework linq-to-sql lambda


【解决方案1】:

要将多个条件与 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 提供程序(或模拟)针对不同的条件进行测试。

【讨论】:

  • 我认为可能的日期范围存在一些缺陷。它以 14.5-18.5 作为最后一条记录结束,
  • Nvm: 发现问题。应该是i &lt;= (PeriodEnd - PeriodStart).TotalDays - StayDuration。让我测试一下,如果一切正常,我会将您的答案标记为完整:)
  • 确实我误解了您如何使用范围。我认为用户设置了 start 期间的范围(即从 1 到 5 开始,停留 10 天)。修改了我的答案。
  • 感谢您的帮助。这现在就像一个魅力。由于您是第一个出现在现场的人,并且您的代码确实可以完成这项工作,因此我会尽快将您的答案标记为正确。如果您对性能提升有任何想法,请写信:)
  • 这取决于您的性能要求。除非你有数千个公寓,否则你可能不应该太担心性能,因为即使是全表扫描也会很快(但通过插入你在实际使用中所期望的尽可能多的测试数据来进行一些测试)。如果性能不够 - 原始 sql 查询可以来救援。长话短说 - 使用预期数量的真实数据进行测试,看看它是否正常。
【解决方案2】:

这是一个纯(不需要外部包)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 };

【讨论】:

  • 当日期范围较高且涉及更多日期组合时,此答案优于先前的答案。谢谢你。我的猜测是 Any 击败了上一个答案中的 All 函数?
  • 其实Any == !All 反之亦然。 stackoverflow.com/questions/36482467/…
  • 我明白了,但是在上面的答案中,我们有 ==All,所以我猜这就是您的查询运行速度更快的原因
  • 我没有分析其他答案,很可能差异来自 EF 如何将查询转换为 SQL - 我的查询在内存表中生成类似 SQL 的常量,这也允许更好的 SQL 计划与. 参数,即消除所谓的参数嗅探问题。
  • 感谢您的澄清。
猜你喜欢
  • 2013-10-19
  • 2016-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-25
  • 2013-03-17
相关资源
最近更新 更多