我有一种感觉,我知道这种行为的原因是什么,但我不知道解决它的最佳方法是什么.
我已经构建了一个LinqToSQL查询:
public IEnumerable<AllConditionByCountry> GenerateConditions(int paramCountryId)
{
var AllConditionsByCountry =
(from cd in db.tblConditionDescriptions...
join...
join...
select new AllConditionByCountry
{
CountryID = cd.CountryID,
ConditionDescription = cd.ConditionDescription,
ConditionID = cd.ConditionID,
...
...
}).OrderBy(x => x.CountryID).AsEnumerable<AllConditionByCountry>();
return AllConditionsByCountry;
}
Run Code Online (Sandbox Code Playgroud)
此查询返回大约9500多行数据.
我是这样从我的控制器调用这个:
svcGenerateConditions generateConditions = new svcGenerateConditions(db);
IEnumerable<AllConditionByCountry> AllConditionsByCountry;
AllConditionsByCountry = generateConditions.GenerateConditions(1);
Run Code Online (Sandbox Code Playgroud)
然后我循环:
foreach (var record in AllConditionsByCountry)
{
...
...
...
Run Code Online (Sandbox Code Playgroud)
这是我认为问题所在:
var rList = AllConditionsByCountry
.Where(x => x.ConditionID == conditionID)
.Select(x => x)
.AsEnumerable();
Run Code Online (Sandbox Code Playgroud)
我正在根据我从上面的查询收集的数据做一个嵌套循环(利用我得到的原始数据AllConditionByCountry.我认为这是我的问题所在.当它对数据进行过滤时,它大大减缓了.
基本上这个过程写出了一堆文件(.json,.html)我首先使用ADO.Net测试了这个,并且运行所有这些记录花了大约4秒钟.使用EF(存储过程或LinqToSql)需要几分钟.
我应该对我正在使用的列表类型做什么,或者只是使用LinqToSql的价格?
我试图返回List<AllConditionByCountry>,IQueryable,IEnumerable从我的GenerateConditions方法.列表花了很长时间(类似于我现在看到的). IQueryable当我尝试执行第二个过滤器时出错(查询结果不能多次枚举).
我在LinqPad中运行了同样的Linq语句,它在不到一秒的时间内返回.
我很乐意添加任何其他信息.
请告诉我.
编辑:
foreach (var record in AllConditionsByCountry)
{
...
...
...
var rList = AllConditionsByCountry
.Where(x => x.ConditionID == conditionID)
.Select(x => x)
.AsEnumerable();
conditionDescriptionTypeID = item.ConditionDescriptionTypeId;
id = conditionDescriptionTypeID + "_" + count.ToString();
...
...
}
Run Code Online (Sandbox Code Playgroud)
TL; DR:您正在对数据库进行9895次查询,而不是一次.您需要重写查询,以便只执行一个查询.看看IEnumerable如何为这样做提供一些提示.
啊,是的,那个for循环是你的问题.
foreach (var record in AllConditionsByCountry)
{
...
...
...
var rList = AllConditionsByCountry.Where(x => x.ConditionID == conditionID).Select(x => x).AsEnumerable();
conditionDescriptionTypeID = item.ConditionDescriptionTypeId;
id = conditionDescriptionTypeID + "_" + count.ToString();
...
...
}
Run Code Online (Sandbox Code Playgroud)
Linq-to-SQL的工作方式与Linq类似,因为它(松散地说)将函数附加到链,以便在枚举可枚举时执行 - 例如,
Enumerable.FromResult(1).Select(x => throw new Exception());
Run Code Online (Sandbox Code Playgroud)
这实际上并不会导致代码崩溃,因为枚举永远不会被迭代.Linq-to-SQL的运作原理类似.所以,当你定义这个:
var AllConditionsByCountry =
(from cd in db.tblConditionDescriptions...
join...
join...
select new AllConditionByCountry
{
CountryID = cd.CountryID,
ConditionDescription = cd.ConditionDescription,
ConditionID = cd.ConditionID,
...
...
}).OrderBy(x => x.CountryID).AsEnumerable<AllConditionByCountry>();
Run Code Online (Sandbox Code Playgroud)
您没有对数据库执行任何操作,您只是指示C#构建一个在迭代时执行此操作的查询.这就是为什么只是声明这个查询很快.
当你进入你的循环时,问题出现了.当您点击for循环时,表示您想要开始迭代迭代AllConditionsByCountry器.这会导致.NET关闭并执行初始查询,这需要时间.
当您调用AllConditionsByCountry.Where(x => x.ConditionID == conditionID)for循环时,您正在构建另一个实际上没有执行任何操作的迭代器.据推测,您实际上使用了rList该循环中的结果,但是,您实际上构建了针对数据库执行的N个查询(其中N是AllConditionsByCountry的大小).
这导致您有效地对数据库执行大约9501次查询的情况 - 1用于初始查询,然后对原始查询中的每个元素执行一次查询.与ADO.NET相比,大幅放缓是因为您可能比原来多了9500个查询.
理想情况下,您应该更改代码,以便对数据库执行一个且仅执行一个查询.你有几个选择:
重写Linq-to-SQL查询,使其看起来像这样
var conditions = AllConditionsByCountry.ToList(); foreach(条件中的var记录){var rList = conditions.Where(....); }
请注意,在该示例中,我正在搜索conditions而不是AllConditionsByCountry- .ToList()将返回已经迭代的列表,因此您不再创建数据库查询.这仍然会很慢(因为你正在做超过9500条记录的O(N ^ 2)),但它仍然比创建9500查询更快,因为它将全部在内存中完成.
我想我应该指出哪些方法会导致IEnumerable被迭代而哪些方法没有.
任何名为As*(例如AsEnumerable<T>())的方法都不会导致枚举被迭代.它本质上是一种从一种类型转换为另一种类型的方式.
任何名为To*(例如ToList<T>())的方法都会导致枚举被迭代.在Linq-to-SQL的情况下,这也将执行数据库查询.任何导致您从可枚举中获取值的方法也会导致迭代.您可以通过创建查询并强制迭代使用ToList()然后搜索该列表来利用此优势- 这将导致比较在内存中完成,这是我在上面演示的内容