LINQ to Entities按周分组

我有一个应用程序,允许用户input他们花在工作上的时间,我试图获得一些良好的报告,这是利用LINQ to Entities。 由于每个TrackedTime都有一个TargetDate ,它只是DateTime的“Date”部分,所以按用户和date对时间进行分组相对比较简单(为简单起见,我省略了“where”子句):

 var userTimes = from t in context.TrackedTimes group t by new {t.User.UserName, t.TargetDate} into ut select new { UserName = ut.Key.UserName, TargetDate = ut.Key.TargetDate, Minutes = ut.Sum(t => t.Minutes) }; 

由于DateTime.Month属性,用户和月份分组只是稍微复杂一点:

 var userTimes = from t in context.TrackedTimes group t by new {t.User.UserName, t.TargetDate.Month} into ut select new { UserName = ut.Key.UserName, MonthNumber = ut.Key.Month, Minutes = ut.Sum(t => t.Minutes) }; 

现在来了棘手的部分。 有按周分组的可靠方法吗? 我尝试了以下基于这种类似的LINQ to SQL问题的回应 :

 DateTime firstDay = GetFirstDayOfFirstWeekOfYear(); var userTimes = from t in context.TrackedTimes group t by new {t.User.UserName, WeekNumber = (t.TargetDate - firstDay).Days / 7} into ut select new { UserName = ut.Key.UserName, WeekNumber = ut.Key.WeekNumber, Minutes = ut.Sum(t => t.Minutes) }; 

但是LINQ to Entities似乎不支持对DateTime对象的算术运算,所以它不知道该怎么做(t.TargetDate - firstDay).Days / 7

我曾经考虑过在数据库中创build一个简单的映射几天到几周的视图,然后将该视图添加到我的entity framework上下文中并join到我的LINQ查询中,但是对于这样的事情来说,这似乎有很多工作要做。 算术方法有一个很好的解决办法吗? 与Linq to Entities一起工作的东西,我可以简单地将它合并到LINQ语句中,而不必触摸数据库? 有什么办法告诉Linq实体如何从另一个date中减去一个date?

概要

我想感谢大家的周到和创造性的回应。 在所有这些来回之后,这个问题的真正答案是“等到.NET 4.0”。 我将给予Noldorin的赏金,给出最有用的答案仍然利用LINQ,特别提到雅各布Proffitt提出一个使用entity framework的响应,而无需在数据库端的修改。 还有其他很好的答案,如果你是第一次看这个问题的话,我强烈build议你阅读所有已经投票的答案以及他们的评论。 这确实是StackOverflowfunction的绝佳例子。 谢谢你们!

您应该能够强制查询使用LINQ to Objects而不是LINQ to Entities进行分组,使用对AsEnumerable扩展方法的调用。

尝试以下操作:

 DateTime firstDay = GetFirstDayOfFirstWeekOfYear(); var userTimes = from t in context.TrackedTimes.Where(myPredicateHere).AsEnumerable() group t by new {t.User.UserName, WeekNumber = (t.TargetDate - firstDay).Days / 7} into ut select new { UserName = ut.Key.UserName, WeekNumber = ut.Key.WeekNumber, Minutes = ut.Sum(t => t.Minutes) }; 

这至less意味着where子句被LINQ to Entities执行,但是对于实体来说太复杂的group子句是通过LINQ to Objects完成的。

让我知道如果你有这个运气。

更新

这里有另外一个build议,可以让你使用LINQ to Entities来完成整个事情。

 (t.TargetDate.Days - firstDay.Days) / 7 

这简单地扩大了操作,以便只执行整数减法而不是DateTime减法。

它目前未经testing,所以它可能会或可能不会工作…

只要您使用SQLServer,您也可以在LINQ中使用SQL函数:

 using System.Data.Objects.SqlClient; var codes = (from c in _twsEntities.PromoHistory where (c.UserId == userId) group c by SqlFunctions.DatePart("wk", c.DateAdded) into g let MaxDate = g.Max(c => SqlFunctions.DatePart("wk",c.DateAdded)) let Count = g.Count() orderby MaxDate select new { g.Key, MaxDate, Count }).ToList(); 

这个样本是从MVP Zeeshan Hirani在这个线程中改编的: 一个MSDN

我的时间跟踪应用程序中正好有这个问题,需要按周报告任务。 我尝试了算术方法,但没有得到任何合理的工作。 我结束了刚刚添加一个新的列与数据库中的周数,并计算每个新行。 突然,所有的痛苦都消失了。 我讨厌这样做,因为我是DRY的忠实信徒,但我需要取得进展。 不是你想要的答案,而是另一个用户的观点。 我会看着这个正确的答案。

好的,这是可能的,但是它是一个巨大的PITA,它绕过了entity framework的LINQ部分。 它花了一些研究,但你可以用SqlServer提供程序特定的函数SqlServer.DateDiff()和“实体SQL”完成你想要的。 这意味着你回到string查询,并手动填充自定义对象,但它做你想做的。 你可能可以通过使用Select而不是foreach循环来改善这一点,但实际上我已经得到了这个devise器生成的YourEntities实体集。

 void getTimes() { YourEntities context = new YourEntities(); DateTime firstDay = GetFirstDayOfFirstWeekOfYear(); var weeks = context.CreateQuery<System.Data.Common.DbDataRecord>( "SELECT t.User.UserName, SqlServer.DateDiff('DAY', @beginDate, t.TargetDate)/7 AS WeekNumber " + "FROM YourEntities.TrackedTimes AS t " + "GROUP BY SqlServer.DateDiff('DAY', @beginDate, t.TargetDate)/7, t.User.UserName", new System.Data.Objects.ObjectParameter("beginDate", firstDay)); List<customTimes> times = new List<customTimes>(); foreach (System.Data.Common.DbDataRecord rec in weeks) { customTimes time = new customTimes() { UserName = rec.GetString(0), WeekNumber = rec.GetInt32(1) }; times.Add(time); } } class customTimes { public string UserName{ get; set; } public int WeekNumber { get; set; } } 

我不使用Linq实体,但如果它支持.Year,.Day .Week以及整数除法和模数,那么应该可以使用Zellersalgorithm/同余来计算周数。

欲了解更多信息,请参阅http://en.wikipedia.org/wiki/Zeller's_congruence

无论付出多less努力,我都会留给你/其他人。

编辑:

无论是维基百科都是错误的,或者我所拥有的forumla不是Zeller的,我不知道它是否有名字,但是在这里

  weekno = (( 1461 * ( t.TransactionDate.Year + 4800 + ( t.TransactionDate.Month - 14 ) / 12 ) ) / 4 + ( 367 * ( t.TransactionDate.Month - 2 - 12 * ( ( t.TransactionDate.Month - 14 ) / 12 ) ) ) / 12 - ( 3 * ( ( t.TransactionDate.Year + 4900 + ( t.TransactionDate.Month - 14 ) / 12 ) / 100 ) ) / 4 + t.TransactionDate.Day - 32075) / 7 

所以应该可以在一个组中使用这个(这可能需要根据你的星期开始的哪一天进行调整)。

条件。 这是我从来没有用过的“真实的”公式。 我手工input(不记得,但我可能已经从一本书中复制),这意味着,虽然我已经三重检查了,但是我的括号是正确的,可能是错误的。 所以,你需要在使用之前validation它是否有效。

就个人而言,除非数据量很大,否则我宁愿返回七次数据并在本地过滤,而不是使用类似的东西(这也许解释了为什么我从来没有使用过这个论坛)

以下是LINQ to Entities支持的方法列表。

我看到的唯一选项是从DateTime.Month和DateTime.Day中检索周数。 像这样的东西:

 int yearToQueryIn = ...; int firstWeekAdjustment = (int)GetDayOfWeekOfFirstDayOfFirstWeekOfYear(yearToQueryIn); from t in ... where t.TargetDate.Year == yearToQueryIn let month = t.TargetDate.Month let precedingMonthsLength = month == 1 ? 0 : month == 2 ? 31 : month == 3 ? 31+28 : month == 4 ? 31+28+31 : ... let dayOfYear = precedingMonthsLength + t.TargetDate.Day let week = (dayOfYear + firstWeekAdjustment) / 7 group by ... 

奇怪的是,没有人发布.NET Framework的GetWeekOfYear方法。

只是做你的第一个select,添加一个虚拟的weeknumber属性,然后循环所有的这些方法来设置正确的星期编号在你的types,它应该是足够快,做两个select之间的一个循环不应该吗?

 public static int GetISOWeek(DateTime day) { return System.Globalization.CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(day, System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday); } 

我认为你唯一的问题是对DateTimetypes的算术运算,请尝试下面的例子。 它将数据库中的操作保留为标量函数,只返回所需的分组结果。 .Days函数不能在你的目标上工作,因为TimeSpan的实现是有限的,并且(大部分)只能在SQL 2008和.Net 3.5 SP1中使用, 这里还有其他一些注意事项 。

 var userTimes = from t in context.TrackedTimes group t by new { t.User.UserName, WeekNumber = context.WeekOfYear(t.TargetDate) } into ut select new { UserName = ut.Key.UserName, WeekNumber = ut.Key.WeekNumber, Minutes = ut.Sum(t => t.Minutes) }; 

将数据库中的函数添加到您的上下文中(如context.WeekOfYear):

 CREATE FUNCTION WeekOfYear(@Date DateTime) RETURNS Int AS BEGIN RETURN (CAST(DATEPART(wk, @Date) AS INT)) END 

有关其他参考: 以下是在LINQ to SQL场景中正确转换的受支持的DateTime方法

另一个解决scheme是, 每日组logging,然后遍历它们。 当你看到周开始,然后进行总结或计数操作。

以下是一个按星期计算收入统计的示例代码。

GetDailyIncomeStatisticsData方法从日常数据库中检索数据。

  private async Task<List<IncomeStastistic>> GetWeeklyIncomeStatisticsData(DateTime startDate, DateTime endDate) { var dailyRecords = await GetDailyIncomeStatisticsData(startDate, endDate); var firstDayOfWeek = DateTimeFormatInfo.CurrentInfo == null ? DayOfWeek.Sunday : DateTimeFormatInfo.CurrentInfo.FirstDayOfWeek; var incomeStastistics = new List<IncomeStastistic>(); decimal weeklyAmount = 0; var weekStart = dailyRecords.First().Date; var isFirstWeek = weekStart.DayOfWeek == firstDayOfWeek; dailyRecords.ForEach(dailyRecord => { if (dailyRecord.Date.DayOfWeek == firstDayOfWeek) { if (!isFirstWeek) { incomeStastistics.Add(new IncomeStastistic(weekStart, weeklyAmount)); } isFirstWeek = false; weekStart = dailyRecord.Date; weeklyAmount = 0; } weeklyAmount += dailyRecord.Amount; }); incomeStastistics.Add(new IncomeStastistic(weekStart, weeklyAmount)); return incomeStastistics; } 

lambda计数? 我从linq语法开始,但是我认为在C#3.0 lambdaexpression式中,当涉及到声明性编程时。

 public static void TEST() { // creating list of some random dates Random rnd = new Random(); List<DateTime> lstDte = new List<DateTime>(); while (lstDte.Count < 100) { lstDte.Add(DateTime.Now.AddDays((int)(Math.Round((rnd.NextDouble() - 0.5) * 100)))); } // grouping var weeksgrouped = lstDte.GroupBy(x => (DateTime.MaxValue - x).Days / 7); } 

您可以尝试从DayOfYear属性获取年份和weekNumber

 var userTimes = from t in context.TrackedTimes group t by new {t.User.UserName, Year = t.TargetDate.Year, WeekNumber = t.TargetDate.DayOfYear/7} into ut select new { UserName = ut.Key.UserName, WeekNumber = ut.Key.WeekNumber, Minutes = ut.Sum(t => t.Minutes) }; 

你可以尝试一个扩展方法来进行转换:

 static int WeekNumber( this DateTime dateTime, DateTime firstDay) { return (dateTime - firstDay).Days / 7; } 

那么你会有:

 DateTime firstDay = GetFirstDayOfFirstWeekOfYear(); var userTimes = from t in context.TrackedTimes group t by new {t.User.UserName, t.TargetDate.WeekNumber( firstDay)} into ut select new { UserName = ut.Key.UserName, WeekNumber = ut.Key.WeekNumber, Minutes = ut.Sum(t => t.Minutes) }; 

那么,这可能是更多的工作取决于数据库如何索引,但你可以按日sorting,然后在客户端做一天 – >星期映射。