Linq优化查询和foreach

时间:2012-02-29 15:01:37

标签: c# .net sql linq

我从Linq查询返回一个List,然后我必须用for循环填充其中的值。 问题是它太慢了。

var formentries = (from f in db.bNetFormEntries
            join s in db.bNetFormStatus on f.StatusID.Value equals s.StatusID into entryStatus
            join s2 in db.bNetFormStatus on f.ExternalStatusID.Value equals s2.StatusID into entryStatus2
            where f.FormID == formID
            orderby f.FormEntryID descending
            select new FormEntry
            {
                FormEntryID = f.FormEntryID,
                FormID = f.FormID,
                IPAddress = f.IpAddress,
                UserAgent = f.UserAgent,
                CreatedBy = f.CreatedBy,
                CreatedDate = f.CreatedDate,
                UpdatedBy = f.UpdatedBy,
                UpdatedDate = f.UpdatedDate,
                StatusID = f.StatusID,
                StatusText = entryStatus.FirstOrDefault().Status,
                ExternalStatusID = f.ExternalStatusID,
                ExternalStatusText = entryStatus2.FirstOrDefault().Status
            }).ToList();

然后我以这种方式使用for:

for(var x=0; x<formentries.Count(); x++)
{
    var values = (from e in entryvalues
                where e.FormEntryID.Equals(formentries.ElementAt(x).FormEntryID)
                select e).ToList<FormEntryValue>();
    formentries.ElementAt(x).Values = values;
}
return formentries.ToDictionary(entry => entry.FormEntryID, entry => entry);

但这绝对太慢了。 有没有办法让它更快?

4 个答案:

答案 0 :(得分:7)

  

绝对太慢了。有没有办法让它更快?

也许。也许不吧。但这不是正确的问题。正确的问题是:

  

为什么这么慢?

如果您对第二个问题有答案,那么找出第一个问题的答案要容易得多!如果对第二个问题的答案是“因为数据库在东京而且我在罗马,而且数据包的移动速度不比光速快,这是我不可接受的减速的原因”,那么你的方式就是你移居日本的速度越快;没有多少修复查询会改变光速。

要弄清楚它为何如此慢,获取一个分析器。通过分析器运行代码,并使用它来确定您花费大部分时间的地方。然后看看你是否可以加速那部分。

答案 1 :(得分:0)

对于我所看到的,当您填充值以及转换为字典时,您正无缘无故地再次迭代formentries 2次。

如果entryvalues是数据库驱动的 - 即从数据库中获取它们,则将值字段填充到第一个查询中。

如果不是,则不需要在第一个查询上调用ToList(),执行循环,然后创建字典。

var formentries = from f in db.bNetFormEntries
                join s in db.bNetFormStatus on f.StatusID.Value equals s.StatusID into entryStatus
                join s2 in db.bNetFormStatus on f.ExternalStatusID.Value equals s2.StatusID into entryStatus2
                where f.FormID == formID
                orderby f.FormEntryID descending
                select new FormEntry
                {
                    FormEntryID = f.FormEntryID,
                    FormID = f.FormID,
                    IPAddress = f.IpAddress,
                    UserAgent = f.UserAgent,
                    CreatedBy = f.CreatedBy,
                    CreatedDate = f.CreatedDate,
                    UpdatedBy = f.UpdatedBy,
                    UpdatedDate = f.UpdatedDate,
                    StatusID = f.StatusID,
                    StatusText = entryStatus.FirstOrDefault().Status,
                    ExternalStatusID = f.ExternalStatusID,
                    ExternalStatusText = entryStatus2.FirstOrDefault().Status
                };

var formEntryDictionary = new Dictionary<int, FormEntry>();

foreach (formEntry in formentries)
{
    formentry.Values = GetValuesForFormEntry(formentry, entryvalues);
    formEntryDict.Add(formEntry.FormEntryID, formEntry);
}

return formEntryDictionary;

价值准备:

private IList<FormEntryValue> GetValuesForFormEntry(FormEntry formEntry, IEnumerable<FormEntryValue> entryValues)
{
    return (from e in entryValues
                    where e.FormEntryID.Equals(formEntry.FormEntryID)
                    select e).ToList<FormEntryValue>();
}

如果愿意,您可以将私有方法更改为仅接受entryId而不是整个formEntry。

答案 2 :(得分:0)

这很慢,因为O(N*M) Nformentries.CountMentryvalues.Count即使进行简单的测试,我的速度也会慢20倍以上我的类型只有1000个元素只有一个int id字段,列表中有10000个元素,它比下面的代码慢1600多倍!

假设你的入门值是一个本地列表,而不是命中一个数据库(如果是这种情况,只需.ToList()到一个新的变量),并假设你的FormEntryId是唯一的(它似乎来自{{ 1}}调用然后尝试这个:

.ToDictionary

要使其至少达到更好的规模,还有很长的路要走。

更改:var entryvaluesDictionary = entryvalues.ToDictionary(entry => entry.FormEntryID, entry => entry); for(var x=0; x<formentries.Count; x++) { formentries[x] = entryvaluesDictionary[formentries[x].FormEntryID]; } return formentries.ToDictionary(entry => entry.FormEntryID, entry => entry); 而不是.Count,因为最好不要在不需要时调用扩展方法。使用字典查找值而不是对.Count()循环中的每个x值进行处理,可以有效地从bigO中删除for

如果这不完全正确,我相信你可以改变任何缺失的东西,以适应你的工作案例。但是,除此之外,您应该考虑使用变量名称Mformentries的案例。一个更容易阅读。

答案 3 :(得分:0)

有一些原因可能导致您使用formentries的方式可能会很慢。

  • 上面的formentries List<T>有一个Count属性,但您正在调用可枚举的Count()扩展方法。此扩展可能有也可能没有优化,可以检测到您正在对具有Count属性的集合类型进行操作,而该属性可以延迟到而不是遍历枚举来计算计数。
  • 类似地,formEntries.ElementAt(x)表达式使用两次;如果他们没有优化ElementAt以确定他们正在使用类似于可以通过索引跳转到项目的列表的集合,那么LINQ将不得不冗余地遍历列表以获取到第x个项目。

上述评估可能会错过真正的问题,如果您的个人资料,您只会真正知道。但是,如果您按如下方式切换formentries集合的迭代方式,则可以避免上述操作,同时使代码更易于阅读:

foreach(var fe in formentries)
{
    fe.Values = entryvalues
        .Where(e => e.FormEntryID.Equals(fe.FormEntryID))
        .ToList<FormEntryValue>();
}
return formentries.ToDictionary(entry => entry.FormEntryID, entry => entry);

您可能已采用for(var x=...) ...ElementAt(x)方法,因为您认为无法修改foreach循环变量fe引用的对象的属性。

尽管如此,另一点可能是一个问题,如果formentries有多个项目具有相同的FormEntryID。这将导致在循环内多次完成相同的工作。虽然顶部查询似乎是针对数据库的,但您仍然可以在linq-to-object域中与数据进行连接。快乐的优化/分析/编码 - 让我们知道什么对你有用。