【问题标题】:Inefficient LINQ Foreach Statement Times out低效的 LINQ Foreach 语句超时
【发布时间】:2017-07-09 23:11:46
【问题描述】:

我有一个问题,在循环我的 foreach 语句时,我的应用程序返回超时错误。请注意,我无法修改或查看正在查询的表。

IEnumerable<MyTable> TContextData;
if (!string.IsNullOrEmpty(id)
    || !string.IsNullOrEmpty(name)
    || !string.IsNullOrEmpty(cell)
    )
{
    TContextData = (from e in db.mytable
                    where (!String.IsNullOrEmpty(e.id))
                    select e);
}
else
{
    TContextData = null;
}

List<EachC> mDatalist = new List<EachC>();

if (TContextData != null)
{
    foreach (var i in TContextData.ToList())
    {
        EachMR EachM = new EachMR();

        EachM.Feature = i.TRuid;
        EachM.Feature2 = i.TRuid2;
        EachM.Feature3 = i.TRuid3;
        EachM.Date = GetInnerTextfromXML(EachM.Feature3, "/Sections/Multiple/Functions/Group/Date");
        mdatalist.Add(EachM);
    }
}

大约有 40K 行需要迭代。有没有办法可以提高效率?

错误消息:System.Data.Entity.dll 中出现“System.OutOfMemoryException”类型的异常,但未在用户代码中处理

我已将 Context 中的超时时间增加到

((IObjectContextAdapter)this).ObjectContext.CommandTimeout = 0;

但无济于事,因为它只是掩盖问题。

【问题讨论】:

  • 您需要mytable 行的全部内容吗?每一行真的需要受到“做事”的影响吗? e.id 可以是空的,或者只是空的,还是真的需要两个检查?如果您在开始迭代之前.ToList() 第二个查询有什么不同吗?您是否需要将更改保存回数据库?您是否在“do stuff”中访问mytable 的子表(注意在for 循环中延迟执行的查询)?您是否对此进行了分析以查看正在运行的 SQL?您可能会执行 40,000 个查询而不是 1 个。
  • 为什么 .Take(1) 会阻止你的 foreach 超时? - 我的意思是,显然它只会返回一行,但为什么你会得到一行而不是做一个空(或空)检查?
  • 您的应用程序是如何托管的,需要多长时间才能超时?此外,您的问题可能不在于您展示的 linq,而在于您尚未向我们展示的 #Do Stuff 部分。您需要分析您的代码,尤其是#Do Stuff 部分,并查看哪个部分花费的时间过长
  • 您的两个查询是同一件事。检查您的连接字符串中是否启用了MultipleResultSets,以防延迟执行使您绊倒。尝试 .ToList() 并将 40k 记录拉入内存以避免这种情况。如果数据过多,请限制您引入的内容。
  • 完整的错误信息是什么?将其包装在 Try/Catch 中并在异常上调用 ToString() 并为我们提供完整的异常。

标签: c# linq foreach


【解决方案1】:

删除.ToList() 可能会有所帮助。您的代码不必要地缓冲列表中查询返回的所有内容,然后遍历该列表。只需遍历TContextData

【讨论】:

    【解决方案2】:

    如果没有确切的消息,很难判断超时从何而来;但我猜这不是 SQL 超时。

    您的代码实际上在做的是针对您尝试在foreach 中检索的每一行对数据库执行查询 - 我的意思是您可能正在执行 40 000 个查询。相反,您应该将数据加载到内存中并使用.ToList() 方法在列表中执行您的代码,该方法将执行一次查询。

    IEnumerable TContextData;
    
    if (!string.IsNullOrEmpty(id)
    || !string.IsNullOrEmpty(name)
    || !string.IsNullOrEmpty(cell))
    {
        TContextData = (from e in db.mytable where (!string.IsNullOrEmpty(e.id)) select e); 
    }
    
    foreach(var i in TContextData.ToList())
    {    
        //do stuff
    }
    

    再一次,我说的是没有所有上下文;如果您使用EFLinqToSQL 或将DataReader 转换为IEnumerable,答案会有所不同...

    【讨论】:

    • 这不是真的 - 除非他没有在循环内的其他任何地方使用 TContextData,否则此调用会创建一个 IEnumerator,并且不会被执行 40000 次。
    • 谢谢戈贝斯。我试过这个,但这是我收到的错误:{“抛出了'System.OutOfMemoryException'类型的异常。”}
    • @decPL 我同意 IEnumerator 只会创建一次,并且只有在代码枚举它时才会执行查询,但是 Enumerator 和枚举之间的区别是什么? List&lt;T&gt; 的枚举,如果不是延迟执行?我总是虽然 EF 为每一行查询数据库(使用一些查询树魔法);我对这种行为有一些问题,使用ToList 大大减少了查询的数量……可能是因为属性导航吗?
    • @gobes 你可能会混淆IEnumerable&lt;T&gt;IQueryable&lt;T&gt; 上的延迟执行。后者的工作方式取决于该接口的具体实现;不记得它对于 EF 的具体情况如何,但我认为它有一些逻辑有助于确定最佳方法(如果我不得不猜测,它是一个单一的查询,并且阅读器通过执行保持打开状态)。为什么不需要一次阅读所有内容,可以在@AMorrisey 的评论中看到改进。
    猜你喜欢
    • 2014-09-02
    • 2014-01-12
    • 2014-05-28
    • 2013-07-01
    • 1970-01-01
    • 2011-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多