查询和foreach的Linq优化

本文关键字:Linq 优化 foreach 查询 | 更新日期: 2023-09-27 18:26:23

我从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);

但肯定太慢了。有没有办法让它更快?

查询和foreach的Linq优化

它肯定太慢了。有没有办法让它更快?

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

为什么这么慢?

如果你有第二个问题的答案,那么第一个问题的回答会容易得多!如果第二个问题的答案是"因为数据库在东京,而我在罗马,而数据包的移动速度不超过光速是我无法接受的放缓的原因",那么你让它更快的方法是你搬到日本;修复查询再多也不会改变光速。

要弄清楚为什么它如此缓慢,获取一个探查器。通过探查器运行代码,并使用它来确定您大部分时间花在哪里。然后看看你是否能加快这部分的速度。

就我所见,当您填充值时,以及当您转换为dictionary时,您在formentries中又无故迭代了2次。

如果entryvalues是数据库驱动的,即您从数据库中获取它们,那么将值字段填充放入第一个查询中。

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

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。

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

假设您的entryValue是一个本地列表,并且没有命中数据库(如果是这样的话,只需将其.ToList()映射到某个新变量),并且假设您的FormEntryId是唯一的(它似乎来自.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(),因为在不需要的时候最好不要调用扩展方法。使用字典查找值,而不是对for循环中的每个x值执行where操作,可以有效地从bigO中删除M

如果这不是完全正确的,我相信你可以根据你的工作情况更改缺失的内容。但顺便说一句,您真的应该考虑为变量名formentriesformEntries使用大小写,这只是更容易阅读一点。

使用formentries的方式可能较慢,这是有一些原因的。

  • 上面的formentries List<T>具有Count属性,但您正在调用可枚举的Count()扩展方法。此扩展可能具有优化,也可能不具有优化,该优化可以检测到您正在对具有Count属性的集合类型进行操作,而不是遍历枚举来计算计数
  • 类似地,formEntries.ElementAt(x)表达式被使用两次;如果他们没有优化CCD_ 24以确定他们正在处理一个集合,比如一个可以通过其索引跳转到项目的列表,那么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中的数据连接到对象land。愉快的优化/分析/编码-让我们知道什么对您有效。