2

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 });** 
1
  • Why do you do both the ToDictionary and the GroupBy? The ToDictionary would appear unnecessary. Commented Sep 24, 2015 at 19:48

3 Answers 3

2

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.

Sign up to request clarification or add additional context in comments.

Comments

1

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. IEnumerables 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(); 

5 Comments

A good primer on linq and lazy-evaluation: blogs.msdn.com/b/ericwhite/archive/2006/10/04/lazy-evaluation-2800_and-in-contrast_2c00-eager-evaluation_2900_.aspx
I had this exact example created, but you beat me to it.
@DVK faster than the speed of DVK!
This worked for me. I was able to breakdown the lambda expressions into individual unchained expressions and then I was able to measure the performance of each. The issue was related to the GetOurProduction method which was making multiple calls to a web service method and subsequently the database when it only needed to make a single call.
@thesverdish I assume that had to do with re-evaluating the IEnumerables multiple times?
1

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...

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.