Reputation: 23
I'm assessing a performance issue in my C# .NET web application and have traced the bottleneck to a chained lambda expression. I would like to remove the lambda expression entirely so I can evaluate the performance of each step in the chain however I am relatively new to lambda expressions. Does anyone have any thoughts on how the second lambda express could be refactored into more traditional code so that each step or action can traced?
IEnumerable<OurPerformance> validPerformances = package.TimeFilteredValidPerformances(visitDateAndTime);
IEnumerable<WebPerformance> webPerformances = performanceGroup.RegularNonPassedPerformances
.Where(performance => validPerformances.Select(perf => perf.PerformanceId).Contains(performance.PerformanceId))
.Select(performance =>
new WebPerformance
{
Date = performance.PerformanceDate.ToJavaScriptDateString(),
PerformanceId = performance.PerformanceId,
Title = performance.UserFriendlyTitle,
ProductionSeasonId = performance.ProductionSeasonId,
AvailableCount = performance.AvailableCount
});
**IEnumerable<WebProduction> webProductions = webPerformances
.GroupBy(performance => performance.ProductionSeasonId)
.ToDictionary(grouping => SheddProduction.GetOurProduction(grouping.Key), grouping => grouping.ToList())
.Select(perfsByProduction =>
new WebProduction
{
ProductionSeasonId = perfsByProduction.Key.ProductionSeasonNumber,
Duration = perfsByProduction.Key.Duration,
Title = perfsByProduction.Key.UserFriendlyTitle,
Synopsis = perfsByProduction.Key.UserFriendlySynopsis,
ThumbnailImage = perfsByProduction.Key.PreviewImage,
Performances = perfsByProduction.Value
});**
Upvotes: 2
Views: 116
Reputation: 1895
Here is lamba-refactoring of your second lambda expression into "traditional" code:
IEnumerable<IGrouping<string, WebProduction>> groupedPerformances
= webPerformances.GroupBy(performance => performance.ProductionSeasonId);
var dictionary = new Dictionary<string, List<WebProduction>>();
foreach (IGrouping<string, WebProduction> grouping in groupedPerformances)
{
var group = new List<WebProduction>();
foreach (WebProduction webProduction in grouping)
group.Add(webProduction);
dictionary.Add(grouping.Key, group);
}
var result = new List<WebProduction>();
foreach (KeyValuePair<string, List<WebProduction>> item in dictionary)
{
var wp = new WebProduction
{
ProductionSeasonId = perfsByProduction.Key.ProductionSeasonNumber,
Duration = perfsByProduction.Key.Duration,
Title = perfsByProduction.Key.UserFriendlyTitle,
Synopsis = perfsByProduction.Key.UserFriendlySynopsis,
ThumbnailImage = perfsByProduction.Key.PreviewImage,
Performances = perfsByProduction.Value
};
result.Add(wp);
}
I do not know result-type of your SheddProduction.GetOurProduction
method, thus I made minor changes, but you can get the gist...
Upvotes: 1
Reputation: 19897
Its actually fairly straight-forward to break this up into smaller pieces which are more obviously converted into "traditional" code. Just store each linq expression result in a local variable like this:
var groupedWebPerformances = webPerformances.GroupBy(performance => performance.ProductionSeasonId);
var webPerformancesDictionary = groupedWebPerformances .ToDictionary(grouping => SheddProduction.GetOurProduction(grouping.Key), grouping => grouping.ToList());
IEnumerable<WebProduction> webProductions = webPerformancesDictionary.Select(perfsByProduction =>
new WebProduction
{
ProductionSeasonId = perfsByProduction.Key.ProductionSeasonNumber,
Duration = perfsByProduction.Key.Duration,
Title = perfsByProduction.Key.UserFriendlyTitle,
Synopsis = perfsByProduction.Key.UserFriendlySynopsis,
ThumbnailImage = perfsByProduction.Key.PreviewImage,
Performances = perfsByProduction.Value
});
That being said, your performance problem is that you're using IEnumerable
everywhere. IEnumerable
s might re-evaluate each time they are used (depends on what the underlying type is), so validPerformances
is getting re-evaluated once for each item in RegularNonPassedPerformances
and each item in webPerformances
is not getting evaluated until the whole enumeration is forced to evaluate, so the performance problem appears to be later but is most likely here:
validPerformances.Select(perf => perf.PerformanceId).Contains(performance.PerformanceId)
Make sure you force all of your enumerables to evaluate a single time by doing a ToList
on them when they are created like this:
List<OurPerformance> validPerformances = package.TimeFilteredValidPerformances(visitDateAndTime).ToList();
List<WebPerformance> webPerformances = performanceGroup.RegularNonPassedPerformances
.Where(performance => validPerformances.Select(perf => perf.PerformanceId).Contains(performance.PerformanceId))
.Select(performance =>
new WebPerformance
{
Date = performance.PerformanceDate.ToJavaScriptDateString(),
PerformanceId = performance.PerformanceId,
Title = performance.UserFriendlyTitle,
ProductionSeasonId = performance.ProductionSeasonId,
AvailableCount = performance.AvailableCount
})
.ToList();
List<WebProduction> webProductions = webPerformances
.GroupBy(performance => performance.ProductionSeasonId)
.ToDictionary(grouping => SheddProduction.GetOurProduction(grouping.Key), grouping => grouping.ToList())
.Select(perfsByProduction =>
new WebProduction
{
ProductionSeasonId = perfsByProduction.Key.ProductionSeasonNumber,
Duration = perfsByProduction.Key.Duration,
Title = perfsByProduction.Key.UserFriendlyTitle,
Synopsis = perfsByProduction.Key.UserFriendlySynopsis,
ThumbnailImage = perfsByProduction.Key.PreviewImage,
Performances = perfsByProduction.Value
})
.ToList();
Upvotes: 1
Reputation: 464
I can't suggest any algorithm to exclude lambda expressions, but I have another suggestion. Try using List for webPerformances instead of IEnumerable collection. Call ToList() for getting List webPerformances. In this case the second lambda expression will work with list and won't reevaluate the IEnumerable collection.
Upvotes: 2