问题:在日期范围内过滤记录并使用LINQ匹配CityID时,查询在以两个步骤编写时成功;但是,当它组合成一个查询时它会失败!
如何重写LINQ查询,以便 - 它可以执行两个过滤器(即匹配CityId
并在同一步骤中检索date range
中的记录以提高性能?
我让它分两步工作,
即做一个
var Step1 = db.weekRecord.Where(x => x.CityId == CityRecord.Id).ToList();
然后
Step1.Where(x => x.date.Date >= fromDate.Date
&& x.date.Date <= toDate.Date)
.ToList();
我把它们结合起来就失败了!!
// works when done in 2 steps!!
var weeklyWeather = db.weekRecord
.Where(x => x.CityId == CityRecord.Id
&& (x.date >= weekStarting && x.date <= weekEnding))
// - when combined results are NULL!??
var weeklyWeather2 =
db.weekRecord(x => x.date.Date >= fromDate.Date && x.date.Date <= toDate.Date)
.ToList();
在查找其他SO answers后,我尝试了这个TruncateTime ......无法让它工作..
// is this correct, from SO answers, DbFunctions.TruncateTime
var testQueryRecrods = db.weekRecord
.Where(x => x.CityId == CityRecord.Id)
.Where(x =>
DbFunctions.TruncateTime(x.date.Date) >= DbFunctions.TruncateTime(fromDate.Date)
&& DbFunctions.TruncateTime(x.date.Date) <= DbFunctions.TruncateTime(toDate.Date))
.ToList();
错误:
[NotSupportedException:LINQ to Entities中不支持指定的类型成员'Date'。仅支持初始化程序,实体成员和实体导航属性。 System.Data.Entity.Core.Objects.ELinq.MemberAccessTranslator.TypedTranslate(ExpressionConverter parent,MemberExpression linq)+452 System.Data.Entity.Core.Objects.ELinq.TypedTranslator`1.Translate(ExpressionConverter parent,Expression linq)+49
问题很困惑,但我认为问题是.Date
。与linq2sql不同,实体框架无法将.Date
转换为sql。但你可以改写它
var fromDateDate = fromDate.Date;
var toDateDate = toDate.Date;
var testQueryRecrods = db.weekRecord
.Where(x => x.CityId == CityRecord.Id)
.Where(x => DbFunctions.TruncateTime(x.date) >= fromDateDate
&& DbFunctions.TruncateTime(x.date) <= toDateDate)
.ToList();
它会起作用。到某一点。在这种情况下,EF产生的实际上是完全愚蠢的。与linq2sql不同,EF生成查询,这不是sargable(在我的情况下*)。它可以比必要的慢几千倍。我建议完全避免转换到日期:
var fromDateDate = fromDate.Date;
var toDateDate1 = toDate.Date.AddDays(1);
var testQueryRecrods = db.weekRecord
.Where(x => x.CityId == CityRecord.Id)
.Where(x => x.date >= fromDateDate
&& x.date < toDateDate1)
.ToList();
正如@juharr指出的那样,当你拆分查询时,你会在服务器上运行前半部分,而在后半部分运行linq到对象。在这种情况下,.Date
可以工作,但是你在上半年下载了比你需要的更多的记录。
*日期时间类型可能是问题,也许它会更好地使用datetime2,我没有测试这个场景
最好的建议是为此编写自己的LINQ扩展。
public static class ext
{
//This extension compares one date to another... if you can call from Linq
public static bool GreaterThan(this DateTime self, DateTime CompareDate
{
if (self.Year > CompareDate.Year) return true;
else if ((self.Year == CompareDate.Year) && (self.Month > CompareDate.Month)
return true;
else if ((self.Year == CompareDate.Year) && (self.Month == CompareDate.Month) && (self.Day > CompareDate.Day))
return true;
return false;
}
}
我认为,除了使用DbFunctions.TruncateTime
for Linq to Entities之外别无选择。因为,作为SQL Server查询Linq to Entities应该执行转换datetime
到date
并且可以使用的最佳方法是DbFunctions.TruncateTime
。我只是调试了DbFunctions.TruncateTime
转换和翻译的查询似乎;
WHERE (convert (datetime2, convert(varchar(255), [Extent1].[CreationDate], 102) , 102)) > @p__linq__0
如您所见,在执行对话时,此处存在冗余的字符串对话。但是,EF会将SQL中的日期时间转换为日期,就像这个'cast(CreationDate as date)'
一样。但事实并非如此。
所以,这里有两种选择。
1-如果你有一个非常庞大的表,性能受冗余字符串对话的影响,你应该在SQL中手动构建你的查询作为存储过程或类似的东西,并从上下文执行它。
2-如果你没有这样的性能考虑;只需使用DbFunctions.TruncateTime(x.date)
var testQueryRecrods = db.weekRecord
.Where(x => x.CityId == CityRecord.Id)
.Where(x => DbFunctions.TruncateTime(x.date) >= fromDate.Date && DbFunctions.TruncateTime(x.date) <= toDate.Date)
.ToList();