Wednesday, 7 September 2011

Why Microsoft, Why?

In one of our Dynamics CRM app we make heavy use of facility/equipment calendars. Ordinarily this would not be even worthy of mention, never mind a post, but Microsoft decided that it would be a bad idea to delete those calendars. Nominally deletion of calendars is supported in the SDK documentation, but in practice, things are a little bit different.

Anyway, the fact of the matter is that we have built up a rather large collection of calendars and calendar rules (90K & 550K last time I checked)  and this is slowing our users down. Some operations are slightly slower, to the point that they have not noticed, whereas others are simply painstakingly slow (A custom screen can take up to 1 minute to load), so I (re)used a very simple method to delete a calendar:

   1 [WebMethod]
   2 public bool Delete(string strEntityName, string strEntityGuid)
   3         {
   4             try
   5             {
   6                 CreateLog("Trace", "Delete:: Delete( string strEntityName, string strEntityGuid)",
   7                                    "1. IN. strEntityName: " + strEntityName +
   8                                    " - strEntityGuid: " + strEntityGuid + " .");
   9 
  10                 // Set up the CRM Service.
  11                 //CrmService lCrmService = new CrmService();
  12                 //lCrmService.Credentials = System.Net.CredentialCache.DefaultCredentials;
  13                 //lCrmService.Url = ConfigurationSettings.AppSettings["CRMServiceURL"];
  14 
  15                 Guid objEntityId = new Guid(strEntityGuid);
  16 
  17                 CreateLog("Trace", "Delete:: Delete( string strEntityName, string strEntityGuid)", "2. About to Call Delete.");
  18 
  19                 lCrmService.Delete(strEntityName, objEntityId);
  20 
  21                 CreateLog("Trace", "Delete :: Delete( string strEntityName, string strEntityGuid)", "3. Delete Complete. Returning True");
  22                 CreateLog("Audit", "Delete :: Delete( string strEntityName, string strEntityGuid)", "Deleted Call with ID: " + strEntityGuid + " .");
  23 
  24                 return true;
  25             }
  26             catch (Exception ex)
  27             {
  28                 CreateLog("Error", "Delete :: Delete( string strEntityName, string strEntityGuid)", "Error: " + ex.ToString() + " .");
  29                 return false;
  30             }
  31         }       

but alas I got the following error message, when I passed in a calendar:
Crm Exception: Message: The object you tried to delete is associated with another object and cannot be deleted., ErrorCode: -2147220953
Fair enough, I thought, I need to delete the calendarrules first, unfortunately I got this error message:
Crm Exception: Message: The 'Delete' method does not support entities of type 'calendarrule'., ErrorCode: -2147219456
No dice. The irritating part is that we can delete the calendars directly with a sql statement, a rather convoluted sql statement actually, but this is:
  1. unsupported
  2. would require a lot of testing
  3. probably definitely a bad idea
After a bit of digging around and a somewhat inspired guess, I have managed to delete calendars, the code, see below, needs to be tested and it's not production ready, you don't say, but it does delete calendars, at least the calendars linked to facilities/equipment, which is the ones we are interested about.

Source Code:
   1 /// <summary>
   2 /// Delete passed in Calendar
   3 /// </summary>
   4 /// <param name="strEntityGuid">Calendar id</param>
   5 /// <returns>true if successful</returns>
   6 [WebMethod]
   7 public bool TDelete(string strEntityGuid)
   8 {
   9     try
  10     {
  11         CreateLog("Trace", "DeleteEntity :: TDelete( string strEntityName, string strEntityGuid)",
  12                            "1. IN. strEntityGuid: " + strEntityGuid + " .");
  13 
  14 
  15         calendar acal = DeleteCalendarRules(strEntityGuid);
  16 
  17         if (acal != null)
  18         {
  19             //Now, we need to find the main calendar for the equipment this calendar (i.e. strEntityGuid) belongs to
  20             //and from that calendar we need to find the calendarrule that we need to delete
  21 
  22             //grab the equipmentid from the name of the calendar so that we can get the equipment this calendar belongs to, remember 
  23             //that calendars are named as equipmentid + date
  24             string equipmentid = System.Text.RegularExpressions.Regex.Match(acal.name.ToUpper(), @"[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{4}-[A-F0-9]{12}").Value;
  25 
  26             //grab the equipment to which calendar strEntityGuid "belongs"
  27             equipment myequip = (equipment)lCrmService.Retrieve(EntityName.equipment.ToString(), new Guid(equipmentid), new AllColumns());
  28 
  29             //grab the calendar for the above equipment. This is because there is a calendarrule reference to this calendar from strEntityGuid
  30             calendar equipmentcal = (calendar)lCrmService.Retrieve(EntityName.calendar.ToString(), myequip.calendarid.Value, new AllColumns());
  31 
  32             //grab the aforementioned calendarrule so that we can set it to null. Note that this should be unique.
  33             string fetch = "<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'><entity name='calendarrule'><attribute name='calendarruleid'/><filter type='and'><condition attribute='calendarid' operator='eq' value='" +
  34          myequip.calendarid.Value.ToString() + "'/><condition attribute='innercalendarid' operator='eq' value='" + strEntityGuid + "'/></filter></entity></fetch>";
  35 
  36             string result = lCrmService.Fetch(fetch);
  37 
  38             XmlDocument doc = new XmlDocument();
  39             doc.LoadXml(result);
  40             string guid = doc.SelectSingleNode("//result").FirstChild.InnerText;
  41 
  42             //loop through all calendar rules for the equipment's calendar 
  43             //and "drop", i.e. set to null, when it matches the result of the fetch query.
  44             //We update the calendar so that the calendarrule is "dropped", this is handled on the background by the platform.
  45             for (int i = 0; i < equipmentcal.calendarrules.Length; i++)
  46             {
  47                 calendarrule mycalrul = equipmentcal.calendarrules[i];
  48 
  49                 if (mycalrul.calendarruleid.Value == new Guid(guid))
  50                 {
  51                     equipmentcal.calendarrules[i] = null;
  52                     lCrmService.Update(equipmentcal);
  53                 }
  54             }
  55 
  56 
  57             TargetDeleteCalendar target = new TargetDeleteCalendar();
  58             target.EntityId = new Guid(strEntityGuid);
  59 
  60 
  61             DeleteRequest delete = new DeleteRequest();
  62             delete.Target = target;
  63 
  64             CreateLog("Trace", "DeleteEntity :: TDelete( string strEntityName, string strEntityGuid)", "2. About to Call Delete.");
  65 
  66             //Delete the bad mother
  67             DeleteResponse deleted = (DeleteResponse)lCrmService.Execute(delete);
  68 
  69             CreateLog("Trace", "DeleteEntity :: TDelete( string strEntityName, string strEntityGuid)", "3. Delete Complete. Returning True");
  70             CreateLog("Audit", "DeleteEntity :: TDelete( string strEntityName, string strEntityGuid)", "Deleted Callaback with ID: " + strEntityGuid + " .");
  71 
  72             return true;
  73         }
  74         else
  75         {
  76             return false;
  77         }
  78     }
  79     catch (Exception e)
  80     {
  81         CreateLog("Error", "DeleteEntity :: TDelete( string strEntityName, string strEntityGuid)", "Error: " + e.ToString() + " - source: " + e.Source + " .");
  82         return false;
  83     }
  84 }
  85 
  86 /// <summary>
  87 /// This doesn't actually delete the calendarrules associated with the passed in calendar.
  88 /// It simply sets its deletionstatecode to 2, so that the deletion service deletes it
  89 /// </summary>
  90 /// <param name="strEntityGuid">Calendar id</param>
  91 /// <returns>calendar entity</returns>
  92 private calendar DeleteCalendarRules(string strEntityGuid)
  93 {
  94     try
  95     {
  96         calendar calTBDeleted = (calendar)lCrmService.Retrieve(EntityName.calendar.ToString(), new Guid(strEntityGuid), new AllColumns());
  97 
  98         //this sets the calendar rules to null. I love this comment :)
  99         calTBDeleted.calendarrules = null;
 100 
 101         lCrmService.Update(calTBDeleted);
 102 
 103         return calTBDeleted;
 104     }
 105     catch (Exception e)
 106     {
 107         //TODO: log the exception don't be lazy
 108         return null;
 109 
 110     }
 111 }

No comments:

Post a Comment