Microsoft Dynamics CRM 4.0 - Thought Repository

Monday, April 27, 2009

Entity Rollup Activities and History for Custom Entities

View Comments


Entity Rollup for Activities in Dynamics CRM 4.0
Warning: This customization is unsupported by Microsoft. Make sure you back up your files in case any undesired affects happen. Research any unsupported changes and test them on your development environment before making any changes to production!
One of the limitations of custom entities created in CRM 4.0 is the inability to perform an entity rollup to show all of a custom entity's activities and the activities of its child entities. A working example of this rollup behavior is exhibited when you look at the Activity or History page for an Account entry. You will see all the Activities for the Account's child record as well as all the Activites for the Account's Contacts(its child entity).


To overcome this limitation, I did some research on how the Activity and History pages for Accounts generate the rollup. I fired up SQL Server Profiler to intercept any queries made to the SQL Server database while I loaded an Account's Activity page within Internet Explorer. The profiler showed that the stored procedure p_RollupByAccount performed all the heavy lifting. The p_RollupByAccount creates a temporary table called #RollupIds which stores all the GUIDs for the Account and its child Contacts and then a subsequent query pulls in all the activities for GUIDs in this table.
Next, I created a custom entity called Manager (cc1_manager) and created a 1:N relationship to Contact (cc1_manager_to_contact) as shown below. Note that the Relationship Attribute is titled cc1_parentmanagerid, which we will use later.
Settings for Custom Entity relationship
Open up the p_RollupByAccount stored procedure and create a backup as we'll be making some changes. The original code has this section at the beginning of the stored procedure to find the currently opened Account, its sub Accounts and their Contacts.
declare @Level int
set @Level = 1
while (@@ROWCOUNT != 0 and @Level <= 5)
BEGIN
 set @Level = @Level + 1
 insert into #RollupIds (RollupId) 
 (
  select distinct AccountId
  from AccountBase acc (NOLOCK)
  join #RollupIds ri on 
  (
   acc.ParentAccountId = ri.RollupId
  )
  where acc.AccountId not in 
  ( 
   select RollupId from #RollupIds
  )
 )
END

insert into #RollupIds (RollupId) 
(
 select distinct ContactId 
 from ContactBase (NOLOCK)
 where AccountId = @AccountId
)
We replace the section above with our modified code below. The code starts by checking to see if the @AccountId GUID passed in is in the cc1_managerBase table. If it is, we'll know it's a Manager GUID and look for Contacts that have a cc1_parentmanagerid set to the Manager GUID. If the GUID passed in is in the AccountBase table, we'll know it is an Account and process it using the original code.
declare @Level int
set @Level = 1
-- ModifiedSQL: Conditional logic to check if passed in @AccountId guid is the guid of a Manager
IF exists  (SELECT cc1_managerId FROM cc1_managerBase WHERE cc1_managerId = @AccountId)
Begin
 -- ModifiedSQL: custom code to add managerID to the Rollup table
 --              @Level has been removed as we have not set a Manager to Manager relationship to establish a Manager/Sub Manager hierarchy
 while (@@ROWCOUNT != 0)
 BEGIN
  insert into #RollupIds (RollupId) 
  (
   select distinct cc1_managerId
   from cc1_managerBase acc (NOLOCK)  -- ModifiedSQL: look in custom entity table cc1_managerBase
   where acc.cc1_managerId not in 
   ( 
    select RollupId from #RollupIds
   )
  )
 END
 insert into #RollupIds(RollupId)
 (
  select distinct c.ContactId
  from ContactBase as c (NOLOCK) 
  join dbo.ContactExtensionBase as ceb (NOLOCK)ON c.ContactId = ceb.ContactId
  join #RollupIds ri on
  (
   ceb.cc1_parentmanagerid = ri.RollupId
  )
  where
   not exists(
    select RollupId from #RollupIds where c.ContactId = RollupId
   )
 )
End
-- ModifiedSQL: Conditional logic to check if passed in @AccountId guid is the guid of an Account
If exists  (SELECT AccountID FROM AccountBase WHERE AccountID = @AccountId)
Begin
 -- original code for account
 while (@@ROWCOUNT != 0 and @Level <= 5)
 BEGIN
  set @Level = @Level + 1
  insert into #RollupIds (RollupId) 
  (
   select distinct AccountId
   from AccountBase acc (NOLOCK)
   join #RollupIds ri on 
   (
    acc.ParentAccountId = ri.RollupId
   )
   where acc.AccountId not in 
   ( 
    select RollupId from #RollupIds
   )
  )
 END 
 insert into #RollupIds (RollupId) 
 (
  select distinct ContactId 
  from ContactBase (NOLOCK)
  where AccountId = @AccountId
 )
End
Now that we have the database half completed, we'll move onto the modifications to the UI. By design, Custom Entities call a different aspx page to pull up all the Activitiy and History items. Whereas Accounts call the page "/sfa/accts/areas.aspx" Custom Entities call "userdefined/areas.aspx" Unfortunately the page used by Custom Entities does not use a stored procedure to generate the entity rollup list, since entity rollups are not a feature of custom entities. This is the reason why I've chosen to use the p_RollupByAccount stored procedure and repuropose "/sfa/accts/areas.aspx" to work for both Accounts and our Custom Entity Managers.
Navigate to the Customizations->Manager->Form->Form Properties->OnLoad Edit page to switch out the old "userdefined/areas.aspx" activity and history page with "/sfa/accts/areas.aspx" The javascript code below locates the "Activity" and "History" elements on the DOM of the page and replaces their onclick behaviour to open up "/sfa/accts/areas.aspx" instead. Since "/sfa/accts/areas.aspx" calls p_RollupByAccount by feeding in the GUID of the currently open object(be it an Account or a Manager), it'll grab the list of activity or history items just as we planned.
var navActivities;
var navActivityHistory;
navActivities = document.all.navActivities;
navActivityHistory = document.all.navActivityHistory;
if (navActivities != null)
{ navActivities.onclick = function() { loadArea('areaActivities');
document.all.areaActivitiesFrame.src="/sfa/accts/areas.aspx?oId=" + crmForm.ObjectId + "&oType=1&security=852023&tabSet=areaActivities";
};}
else
{
alert("navActivities Not Found");
}
if (navActivityHistory != null)
{ navActivityHistory.onclick = function() { loadArea('areaActivityHistory');
document.all.areaActivityHistoryFrame.src="/sfa/accts/areas.aspx?oId=" + crmForm.ObjectId + "&oType=1&security=852023&tabSet=areaActivityHistory";
};}
else
{
alert("navHistory Not Found");
}

Publish the changes to the Manager entity and you now have the ability to have activity entity rollups for custom entities. Use it wisely!

blog comments powered by Disqus

About Me

Henry Bow
I'm a programmer living in sunny Orange Country, California. Since the beginning of 2008, I've been developing on the MS Dynamics CRM 4.0 platform. This blog will help me jot down some of the tips and neat features I've developed along the way while giving me a chance to dabble into the curious world of analytics and SEO.

Please let me know if I can help with your CRM needs.
hbow27@gmail.com

Feed Rss

Subscribe to new posts via e-mail

Recent Posts