运行 IQueryable.First() 时的性能问题
Posted
技术标签:
【中文标题】运行 IQueryable.First() 时的性能问题【英文标题】:performance issues when running IQueryable.First() 【发布时间】:2017-07-18 12:23:59 【问题描述】:我正在为一所大学制作一个程序,该程序会吸引活跃的学生,然后生成一份报告供其他流程使用。
其中一个重要功能是查看学生是否已毕业。
如果学生毕业后没有回来,他们不被视为活跃学生。 如果他们毕业后要回来或没有毕业并要再回来一个学期,则认为他们是活跃的。当一个学生通过 main 函数时,运行它大约需要 5 秒。我发现参与该过程的大部分时间来自此函数中的IQueryable.First()
。
public static bool ContinuingEducation(string v)
var TERMSSTU = from t in _DB.TERMs
join stu in _DB.STUDENT_TERMS_VIEWs
on t.TERMS_ID equals stu.STTR_TERM
where v == stu.STTR_STUDENT
orderby t.TERM_START_DATE descending
select new startdate = t.TERM_START_DATE ;
var graduation = from a in _DB.ACAD_CREDENTIALs
where v == a.ACAD_PERSON_ID
orderby a.ACAD_COMMENCEMENT_DATE ascending
select a;
if (graduation.Count() > 0 && TERMSSTU.Count() > 0)
if (TERMSSTU.First().startdate > graduation.First().ACAD_COMMENCEMENT_DATE) // the problem is here
return true;
return false;
我不知道为什么在这里需要这么长时间。有没有更好的方法来写出这个函数,所以它更快?
【问题讨论】:
您正在运行 4 个查询。 2 用于计数,然后再 2 用于获取每个查询的第一行。相反,只需使用FirstOrDefault
并检查结果是否为null
以确定是否有多个。
您的问题解决了吗 Franco?
我在下面发布了我的解决方案
【参考方案1】:
您使用Count
效率低下,因为您需要额外查询数据库(一次获取Count
,一次获取First
)。以下代码更改将消除获取Count
的需要。
变化:
if (graduation.Count() > 0 && TERMSSTU.Count() > 0)
if (TERMSSTU.First().startdate > graduation.First().ACAD_COMMENCEMENT_DATE) // the problem is here
return true;
return false;
到:
var grad = graduation.FirstOrDefault();
var term = TERMSSTU.FirstOrDefault()
if (grad == null || term == null)
return false;
return term.startdate > grad.ACAD_COMMENCEMENT_DATE;
请注意,即使进行此更改后,FirstOrDefault
仍然可能很慢。要解决此问题,您应该运行SQL Trace
以查看正在生成的 SQL。然后看add indexes/优化查询。
【讨论】:
添加到这个答案:First
比Count
慢的原因是First
需要执行order by
以确保它获得第一个元素根据顺序。【参考方案2】:
试用代码
public static bool ContinuingEducation(string v)
var TERMSSTU = (from t in _DB.TERMs.AsQueryable()
join stu in _DB.STUDENT_TERMS_VIEWs.AsQueryable()
on t.TERMS_ID equals stu.STTR_TERM
where v == stu.STTR_STUDENT
orderby t.TERM_START_DATE descending
select new startdate = t.TERM_START_DATE ).FirstOrDefault();
var graduation = (from a in _DB.ACAD_CREDENTIALs.AsQueryable()
where v == a.ACAD_PERSON_ID
orderby a.ACAD_COMMENCEMENT_DATE ascending
select a).FirstOrDefault();
if (graduation==null || TERMSSTU==null)
return false;
return TERMSSTU.startdate >graduation.ACAD_COMMENCEMENT_DATE;
【讨论】:
【参考方案3】:性能问题不在First
调用中,而是在您的查询中。当您调用 First
时会对其进行评估。
您应该重写代码以使用更少的查询:您可以在 v
参数上加入两个查询,然后获取结果并检查它的日期。它将产生一个查询。
【讨论】:
【参考方案4】:为了提高效率,我最好的办法是在我的程序开始时查询这些记录,这样所有记录都在内存中,这使得查询程序变得轻而易举。
我创建了一个新类来捕获这两个查询
公开课 TERMSTU 公共日期时间?开始日期 得到;放; 公共字符串 ID 获取;放;
公开课毕业 公共日期时间? ACAD_COMMENCEMENT_DATE 获取;放; 公共字符串 ID 获取;放; 公开课报告 私人清单 TERMSSTUS; 私人名单毕业; 公共报告(ColleagueDataContext _DB) var TERMSSTU = 来自 _DB.TERMs 中的 t 在 _DB.STUDENT_TERMS_VIEWs 中加入 stu 在 t.TERMS_ID 上等于 stu.STTR_TERM orderby t.TERM_START_DATE 降序 选择新的 startdate = t.TERM_START_DATE,id = stu.STTR_STUDENT ; TERMSSTUS = 新列表(); foreach(TERMSSTU 中的变量 i) TERMSSTUS.Add(new TERMSTU() Id = i.id, startdate = i.startdate );
var graduation = from a in _DB.ACAD_CREDENTIALs
where a.ACAD_COMMENCEMENT_DATE != null
where a.ACAD_COMMENCEMENT_DATE < DateTime.Today
orderby a.ACAD_COMMENCEMENT_DATE descending
select a;
GRADUATIONS = new List<Graduation>();
foreach (var i in graduation)
GRADUATIONS.Add(new Graduation() Id = i.ACAD_PERSON_ID, ACAD_COMMENCEMENT_DATE = i.ACAD_COMMENCEMENT_DATE );
public List<TERMSTU> givestu()
return TERMSSTUS;
public List<Graduation> givegrad()
return GRADUATIONS;
我修改了函数以获取两个查询
public static bool ContinuingEducation(string v, List<TERMSTU> t, List<Graduation> g)
var term = (from stu in t where stu.Id == v select stu).FirstOrDefault();
var grad = (from gradu in g where gradu.Id == v select gradu).FirstOrDefault();
return term.startdate > grad.ACAD_COMMENCEMENT_DATE.Value.AddDays(-14);
这种方法要写的代码比较多,但是当程序需要遍历 3000 条左右的记录时,它会节省很多时间。
【讨论】:
每个stu
是否有唯一的Id
?每个gradu
是否都有唯一的Id
?如果是这样,您应该将它们存储在 Dictionary
而不是 List
中,以戏剧性地加快速度。
在您的ContinuingEducation
方法中,您还应该考虑如果term
或grad
为空,您的代码应该做什么。原样的代码会抛出异常。以上是关于运行 IQueryable.First() 时的性能问题的主要内容,如果未能解决你的问题,请参考以下文章