查询和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);
但肯定太慢了。有没有办法让它更快?
它肯定太慢了。有没有办法让它更快?
也许吧。也许不是。但这不是一个正确的问题。正确的问题是:
为什么这么慢?
如果你有第二个问题的答案,那么第一个问题的回答会容易得多!如果第二个问题的答案是"因为数据库在东京,而我在罗马,而数据包的移动速度不超过光速是我无法接受的放缓的原因",那么你让它更快的方法是你搬到日本;修复查询再多也不会改变光速。
要弄清楚为什么它如此缓慢,获取一个探查器。通过探查器运行代码,并使用它来确定您大部分时间花在哪里。然后看看你是否能加快这部分的速度。
就我所见,当您填充值时,以及当您转换为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)
,其中N
是formentries.Count
,M
是entryvalues.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
。
如果这不是完全正确的,我相信你可以根据你的工作情况更改缺失的内容。但顺便说一句,您真的应该考虑为变量名formentries
和formEntries
使用大小写,这只是更容易阅读一点。
使用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。愉快的优化/分析/编码-让我们知道什么对您有效。