在C#中优化非常慢的以下代码

时间:2014-01-24 05:23:33

标签: asp.net sql asp.net-mvc linq asp.net-mvc-4

我正在使用ASPX.NET MVC4。我想通过一个模型从数据库中提取一些数据,我想在视图的图表中显示这些数据。在图表中我想显示最多70个数据点(日期,值),但不是更多。

我的模型StudentGrades由以下

组成
StudentID, ModuleID, TestDate, TestGrade.

我编写了以下代码,实际上是这项工作,但是真的慢,并且它所需要的时间是不可接受的。

你知道我如何真正优化我的代码,(通过更改查询,数据结构,循环或其他任何东西)?

我的代码如下所示。

var query = (from b in db.StudentGrades
                         where b.StudentID.Equals(InputStudentID)
                         orderby b.Date
                         select b);

            var dates = query.Select(x => EntityFunctions.DiffDays(query.Min(y => y.Date), x.Date));
            var grades = query.Select(x => x.grades);
            var datestable = dates.ToArray();
            var gradetable = grades.ToArray();
            List<int?> dateslist = new List<int?>();
            List<double> gradelist = new List<double>();
            double result = dates.Count() * 1.0 / 70.0;
            int pointStep = (int)Math.Ceiling(result);
            for (int i = 0; i < dates.Count(); i = i + pointStep)
            {
                dateslist.Add(datestable.ElementAt(i));
                gradelist.Add(gradetable.ElementAt(i));
            }
            ViewBag.dates = dateslist;
            ViewBag.grades = gradelist;

非常感谢

修改

我忘了提。我不只是想要70分。然后我就可以做一下(70)。我真正想要的是根据他们的日期统一从我的数据中得到的70分。所以,例如,如果我在我的数据库中为特定学生提供以下记录,而不是70分我想要3:

ModuleID, TestDate, TestGrade
23, 1 January 2014, 5
34, 2 January 2014, 54
45, 3 January 2014, 35
56, 4 January 2014, 55
67, 5 January 2014, 35
78, 6 January 2014, 56
89, 7 January 2014, 53
90, 8 January 2014, 55
94, 9 January 2014, 57 

我会选择2014年1月1日,2014年1月4日和2014年1月7日的记录或类似的记录。我的意思是我想要的记录应该在它们之间具有相等或相对相等的距离(日期)。

这也是我上面有pointStep变量的原因。

编辑2

另外,你能想出一个非常聪明的方法在查询中完成这个(不添加id,因为这会改变我的模型)?如果没有,那没关系,我也做不到。

非常感谢

3 个答案:

答案 0 :(得分:1)

对每一行执行术语query.Min(y => y.Date)。你为什么不拉一次最小日期?

var minDate = query.Min(y => y.Date);
var dates = query.Select(x => EntityFunctions.DiffDays(minDate, x.Date));

此外,将对以下每一行执行查询

var datestable = dates.ToArray();
var gradetable = grades.ToArray();

所以你的查询被执行(N + 3)次,其中N是行数。这可能发生在服务器中,但仍然是一个非常缓慢的解决方案。

解决方案:将query放在最开头的数组中,以避免多次执行查询。

var query = (from b in db.StudentGrades
                     where b.StudentID.Equals(InputStudentID)
                     orderby b.Date
                     select b).ToArray(); // <---- ToArray() does the trick.

但是你仍然可以通过存储一次最小日期来清理一下。这也使代码更具可读性。试试这个:

var query = (from b in db.StudentGrades
                     where b.StudentID.Equals(InputStudentID)
                     orderby b.Date
                     select b).ToArray();
var minDate = query.Min(y => y.Date);
var dates = query.Select(x => EntityFunctions.DiffDays(minDate, x.Date));
// Nothing changed after this point
var grades = query.Select(x => x.grades);
...

这会提高性能吗?

答案 1 :(得分:1)

首先进行一次查询以获取最短日期并将其存储在minDate

然后你可以这样做:

var query  = from b in db.StudentGrades
             where b.StudentID == InputStudentID
             orderby b.Date
             select x => new { Day = EntityFunctions.DiffDays(minDate, x.Date),
                               Grade = x.grades
                             }

然后获取DaypointStep的倍数的记录:

var results = query.Where(x => x.Day % pointStep == 0).ToList();
var dateslist = results.Select(x => x.Day).ToList();
var gradelist = results.Select(x => x.Grade).ToList();

现在,您将只从一个查询中获取数据库中所需的数据,而不是更多内容(嗯,两个,坦率地说)。建立最终名单就在眨眼之间发生。

答案 2 :(得分:0)

像Entity Framework这样的ORM非常棒,但您需要非常非常密切关注您正在做的事情,否则您最终会扼杀数据库。要遵循的一般规则是只在计算表达式后才执行查询。将导致表达式被评估的事情包括枚举,转换为列表,数组等,请求计数等等。基本上,任何需要真实数据的内容都会导致评估。

那就是说,你还需要注意操作的顺序。在计算表达式之前发出Select not 会导致表达式被评估:它只会添加到最终将执行的查询中。另一方面,如果在之后发出Select ,则执行查询,只要使用该查询提取所有必要数据,就不需要另外查询。

因此,基于此,您可以通过对第一行进行评估来立即改善加载时间:

var query = (from b in db.StudentGrades
                     where b.StudentID.Equals(InputStudentID)
                     orderby b.Date
                     select b).ToList();

通过强制转换到列表,查询将立即执行,其余代码将与内存中的数据进行交互。现在使用您的代码,您将为您投射到数组的每一行发出一个查询,并在您稍后请求计数时发出另一个查询。

现在,虽然你的代码效率很低,但我没有看到任何需要花费大量时间的事情,即使发出一些不必要的查询。尽管如此,你仍然希望摆脱那些效率低下的问题。因此,在我将代码从14行压缩到3时,请注意:

var query = (from b in db.StudentGrades
                     where b.StudentID.Equals(InputStudentID)
                     orderby b.Date
                     select b).ToList();
ViewBag.dates = query.Select(x => EntityFunctions.DiffDays(query.Min(y => y.Date), x.Date)).Take(70);
ViewBag.grades = query.Select(x => x.grades).Take(70);

你有一个 ton 的代码只是为了简单地将结果限制为70个数据点;为此,您可以使用Take并将其称为一天。但是,实际上你可以添加一个最终的优化,它可能实际上是票,因为,就像我之前说过的那样,你没有任何真正疯狂的数据库代码。因此,缓慢很可能是由于此表中的行数量很大,以及您最初将所有内容都拉进来的事实。因此,将Take调用移出ViewBag.dates和{{ 1}}行,然后将其放在ViewBag.grades上,而不是query

ToList

在那里,现在您的查询将只引入前70行并立即执行。您选择的两个选项将操作内存中的数据,而不是发出其他查询。