我有一系列非常复杂的linq查询,除了一件事情外,它可以完成我需要做的所有事情。
首先让我解释一下它的作用。
这一系列查询获取有关游戏会议的数据,并基于相同的EventSession.SessionEventName
和相同的gameID
连接SessionDate
。
因此,在视图中,它看起来像这样:
GameID | GameName | Names | PlayDate | PlayDuration
123 Pac-Man Joe; Jim; Mary 10/1/2018 10:00 AM - 12:30 PM
所以一切正常,除了...我需要将其添加到查询中,但我不知道该放在哪里或如何做:
我需要添加一个条件,如果同一天的两场比赛之间的间隔超过35分钟,则将行分开。
例如,如果乔,吉姆或玛丽的上场时间间隔超过35分钟,则上面的内容看起来像这样。
EventID | GameID | GameName | Names | PlayDate | PlayDuration
-----------------------------------------------------------------------------------------
1 123 Pac-Man Joe; Jim 10/1/2018 10:00 AM - 11:00 AM
2 123 Pac-Man Mary 10/1/2018 11:40 AM - 12:30 PM
因此,基本上,如果GameID和PlayDate相同,它将玩家分组在一起。但是,如果任何地方有35分钟以上的间隔,它将被分成一个新的行分组。
我不知道该怎么做。
我将不胜感激。
谢谢!
这是我的控制器方法,其中包含以下魔术代码:
[HttpPost]
public async Task<IActionResult> SearchByDate(DateTime? start, DateTime? end)
{
// create a list of conferenceGames
var conferenceGames = await _context.PublisherList
.Where(m => m.MakerCatalogId != null
&& !m.IsBetaStatus).ToListAsync();
// create a list of conferencePlayers
var conferencePlayers = await _context.PlayerList
.Where(p => p.PlayerTypeId == 23
&& p.PlayerStartTime != null
&& p.PlayerEndTime != null)
.OrderBy(p => p.PlayerStartTime).ToListAsync();
// create a list of conferenceSponsers
var conferenceSponsers = await _context.SponserList
.Where(f => f.SponserDateTimeStart >= start
&& f.SponserDateTimeStart <= end
&& f.PublisherId != 2000111
).ToListAsync();
// create an IEnumerable of EventSession
var query = from cp in conferencePlayers
join cs in conferenceSponsers on cp.SponserId equals cs.SponserId
join cg in conferenceGames on cs.PublisherId equals cg.PublisherId
select new EventSession
{
Id = cp.PlayerId,
GameId = cg.PublisherId,
SessionGameName = cg.GameDisplayName,
SessionEventName = cp.PlayerDisplayName,
SessionDate = cs.SponserDateTimeStart,
SessionStartTime = cp.PlayerStartTime.Value.TimeOfDay,
SessionEndTime = cp.PlayerEndTime.Value.TimeOfDay
};
// order the results
var orderedResults = query
.OrderBy(n => n.GameId)
.ThenBy(d => d.SessionDate)
.ThenBy(tsa => tsa.SessionStartTime)
.ToList();
// group the List by Date and Game
List<GroupedEvents> playersGroupList = orderedResults.GroupBy(x => new { x.SessionDate, x.GameId }).Select(group => new GroupedEvents
{
GameName = group.Select(n => n.SessionGameName).FirstOrDefault(),
GameId = group.Select(c => c.GameId).FirstOrDefault().ToString(),
PlayDate = group.Select(d => d.SessionDate).FirstOrDefault(),
Names = String.Join(" ; ", group.Select(g => g.SessionEventName).ToArray()),
PlayDuration = group.Select(g => g.SessionStartTime).First() + " - " + group.Select(g => g.SessionEndTime).Last(),
}).ToList();
// return the ordered/grouped list back to the view
return View(playersGroupList);
}
答案 0 :(得分:1)
使用一些扩展方法,可以很容易地做到这一点。
首先,作为Aggregate
的变体,它是APL扫描运算符的一个版本,沿着IEnumerable
行进,返回中间结果,但是该变体组合了一次,当前和上一项的对:
// TKey combineFn((TKey Key, T Value) CurKeyItem, T nextItem):
// CurKeyItem.Key = Current Key
// CurKeyItem.Value = Current Item
// NextItem = Next Item
// returns (Key, Current Item)
public static IEnumerable<(TKey Key, T Value)> ScanToPairs<T, TKey>(this IEnumerable<T> src, TKey seedKey, Func<(TKey Key, T Value), T, TKey> combineFn) {
using (var srce = src.GetEnumerator())
if (srce.MoveNext()) {
var curkv = (seedKey, srce.Current);
while (srce.MoveNext()) {
yield return curkv;
curkv = (combineFn(curkv, srce.Current), srce.Current);
}
yield return curkv;
}
}
说明:ScanToPairs
从IEnumerable
开始,从第一个和第二个值以及seedKey值开始。它将包含当前Key和当前项目以及(单独)下一个项目的ValueTuple
传递到CombineFn,并生成ValueTuple
的Key当前项目。因此,第一个结果是(seedKey,FirstItem)。第二个结果将是(combineFn((seedKey,FirstItem),SecondItem),SecondItem)。依此类推。
然后是一个GroupBy
运算符,该运算符通过使用布尔测试函数对对进行分组:
// bool testFn(T prevItem, T curItem)
// returns groups by runs of matching bool
public static IEnumerable<IGrouping<int, T>> GroupByPairsWhile<T>(this IEnumerable<T> src, Func<T, T, bool> testFn) =>
src.ScanToPairs(1, (kvp, cur) => testFn(kvp.Value, cur) ? kvp.Key : kvp.Key + 1)
.GroupBy(kvp => kvp.Key, kvp => kvp.Value);
说明:使用ScanToPairs
方法,此方法将IEnumerable
分组为元组,其中键是一个以1
开头的整数,表示true
的运行次数testFn
是通过将前一项与当前项进行比较而得出的。对所有运行编号后,它们将与GroupBy
一起分组为属于运行的项目组。
有了这些助手,它就相对简单了。在第一个分组之后添加SelectMany
,以根据基于时间的条件将每个分组划分为子分组:
var playersGroupList = orderedResults.GroupBy(x => new { x.SessionDate, x.GameId })
.SelectMany(g => g.GroupByPairsWhile((p, c) => c.SessionStartTime-p.SessionEndTime <= TimeSpan.FromMinutes(35)))
.Select(group => new GroupedEvents {
GameName = group.Select(n => n.SessionGameName).FirstOrDefault(),
GameId = group.Select(c => c.GameId).FirstOrDefault().ToString(),
PlayDate = group.Select(d => d.SessionDate).FirstOrDefault(),
Names = String.Join("; ", group.Select(g => g.SessionEventName).ToArray()),
PlayDuration = group.Select(g => g.SessionStartTime).First() + " - " + group.Select(g => g.SessionEndTime).Last(),
})
.ToList();
因此,SelectMany
将每个组放在给定的SessionDate
上,然后将它们分组为运行,其中每个成员距下一个成员少于35分钟。由于SelectMany
,所有子组都被提升为最终结果的组。因此,现在您有了分组,每个分组包含一组会话,从SessionEndTime
到下一个SessionStartTime
不到35分钟。请注意,无论何时,跑步都会在一天结束时结束,因此,如果您的跑步时间是午夜,则需要更改分组。
注意:如果同时开始的会话可能具有不同的持续时间(即结束时间),那么您需要在ThenBy(tsa => tsa.SessionEndTime)
排序中添加orderedResults
。