I have three data types in a Parent -> Child -> Grandchild relationship as follows:
Mission -> Activity -> Project
where they contain the parent IDs for relation (i.e. ‘Project’ contains an ‘Activity’ ID). I have some code I use to generate a jQuery nested accordion setup so the the user can click on a ‘Mission’ to see related ‘Activity’s, and can then click on an ‘Activity’ to see the related ‘Project’s.
The code I have takes about six seconds from hitting the page to grab the data from the database and then populating the page. This is far too long and would like to optimize my code in anyway possible. Using miniprofiler (miniprofiler.com) I can see it makes 131 calls to the database with a lot duplicate calls but I’m sure why.
Any help you can give me is greatly appreciated!
LINQ query I use to get all the data and organize it:
public IEnumerable<MissionWithActivities> GetTierTree()
{
var q = from mission in _context.tMissions
join activity in _context.tActivities on mission.id equals activity.missionId
join project in _context.tDefaultEventTypes on activity.id equals project.activityId
where !project.isRemoved && project.defaultCategoryId == 4
orderby mission.id, activity.id, mission.name
select new DefaultEventType(project.tierLevel.TryParseEnum<GanttType>(GanttType.Unknown), DefaultCategoryRepository.CreateFrom(project.tDefaultCategory))
{
AllowNumericSuffix = project.allowNumericSuffix,
AttachMilestoneMoniker = project.attachMilestoneMoniker,
Description = project.description,
Id = project.id,
IsReadOnly = project.isReadOnly,
IsSticky = project.isSticky,
Name = project.name,
Sid = project.sid,
Style = project.style.TryParseEnum<GanttElementStyle>(GanttElementStyle.Unknown),
TimeStamp = project.createdDT,
UpdatedTimeStamp = project.updatedDT,
Activity = new Activity { Id = activity.id, Name = activity.name, Mission = new Mission { Id = mission.id, Name = mission.name } }
};
var q2 = q.GroupBy(
e => e.Activity.Mission.Id,
(mid, events) => new MissionWithActivities
{
Mission = events.First().Activity.Mission,
Activities = events.GroupBy(
e => e.Activity.Id,
(aid, events2) => new ActivityWithEvents
{
Activity = events2.First().Activity,
Events = events2
})
});
return q2.ToList();
}
Code-behind I use to initially populate a datalist and then the nested accordions:
public void SetTierTree(IEnumerable<MissionWithActivities> tierList)
{
dlMission.DataSource = tierList;
dlMission.DataBind();
}
public void dlMission_ItemDataBound(Object sender, DataListItemEventArgs e)
{
DataListItem item = e.Item;
MissionWithActivities mwa = (MissionWithActivities)item.DataItem;
var dlActivity = (DataList)item.FindControl("dlActivity");
dlActivity.DataSource = mwa.Activities;
dlActivity.DataBind();
var i = 0;
foreach (var project in mwa.Activities)
{
DataListItem pItem = dlActivity.Items[i];
var lbCreateNewProject = (LinkButton)pItem.FindControl("lbCreateNewProject");
lbCreateNewProject.CommandArgument = project.Activity.Id.ToString();
var dlProject = (DataList)pItem.FindControl("dlProject");
dlProject.DataSource = project.Events;
dlProject.DataBind();
i++;
var j = 0;
foreach (var data in project.Events)
{
DataListItem lblItem = dlProject.Items[j];
var lbEditProject = (LinkButton)lblItem.FindControl("lbEditProject");
var lbRemoveProject = (LinkButton)lblItem.FindControl("lbRemoveProject");
lbEditProject.CommandArgument = data.Id.ToString();
lbRemoveProject.CommandArgument = data.Id.ToString();
j++;
}
}
}
This is the .aspx page with the jQuery (I didn’t remove the .net so it won’t run) but I wanted to include the code for your perusal:
$(document).ready(function () {
$("html").addClass("js");
$(".row").mouseover(function () { $(this).addClass("over"); }).mouseout(function () { $(this).removeClass("over"); });
$('h5').click(function () { $(this).prev(".heading_add").toggle(); });
$(".row:even").addClass("alt");
$.fn.accordion.defaults.container = false;
$(function () {
$("#acc1").accordion({
el: ".h",
head: "h4, h5",
next: "div",
initShow: "none"
});
$("html").removeClass("js");
});
});
<div id="main">
<ul id="acc1" class="accordion">
<asp:DataList ID="dlMission" runat="server" style="width:600px;">
<ItemTemplate>
<li>
<h4><%# Eval("Mission.Name") %></h4>
<div class="inner">
<ul>
<asp:DataList ID="dlActivity" runat="server">
<ItemTemplate>
<li>
<asp:LinkButton ID="lbCreateNewProject" CausesValidation="false" CssClass="heading_add" runat="server" Text="[ + ] Add New Project Type" OnCommand="lbCreateNewProject_OnCommand" />
<h5><%# Eval("Activity.Name") %></h5>
<div class="inner">
<asp:DataList ID="dlProject" runat="server">
<ItemTemplate>
<div class="row">
<%# Eval("Name") %><div class="action_buttons"><asp:LinkButton ID="lbEditProject" CausesValidation="false" runat="server" Text="Edit" OnCommand="lbEditProject_OnCommand" /> <asp:LinkButton ID="lbRemoveProject" CausesValidation="false" runat="server" Text="Remove" OnCommand="lbRemoveProject_OnCommand" /></div><br />
</div>
</ItemTemplate>
</asp:DataList>
</div>
</li>
</ItemTemplate>
</asp:DataList>
</ul>
</div>
</li>
</ItemTemplate>
</asp:DataList>
</ul>
</div>
For the curious I am using the jQuery.nestedAccordion.js plugin to do this.
I figured out the main cause of the slowness I was experiencing had to do with the way LINQ works. My main issue was this in the LINQ query:
The CreateFrom function was causing LINQ to grab all data associated with DefaultCategory which was extensive. I was unaware that LINQ would grab every relationship all the way down the tree and return it whether it was being used or not. You can read about it here for more info:
http://msdn.microsoft.com/en-us/library/bb738633(v=vs.100).aspx
MiniProfiler still says a duplicate call is being made 131 times, but I think that has to do with the way .Net runs things, if I step through the code it only hits the query once so still working that part out.
Hope this helps someone!