Entity Framework 日期组合查询
Entity Framework date combination query
我有一个 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;
}
有什么建议吗?
要用 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 提供程序(或模拟)明确地测试它。
这是一个纯(不需要外部包)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))))));
此解决方案的好处是可以轻松扩展。上面的查询 returns 可用公寓,但不提供可用房间和时间的信息 - 您可能需要向用户提供这些信息。使用上述方法,您可以获得如下信息:
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 };
我有一个 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;
}
有什么建议吗?
要用 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 提供程序(或模拟)明确地测试它。
这是一个纯(不需要外部包)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))))));
此解决方案的好处是可以轻松扩展。上面的查询 returns 可用公寓,但不提供可用房间和时间的信息 - 您可能需要向用户提供这些信息。使用上述方法,您可以获得如下信息:
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 };