Yesterday I did a complete brain fart. I was an idiot, I made a mistake and I admit it.
“There’s no way that you can live an adequate life without many mistakes. In fact, one trick in life is to get so you can handle mistakes. Failure to handle psychological denial is a common way for people to go broke.”
Charlie Munger on Mistakes
Good advice is to admit your mistakes fast and move on.
The earlier you admit making a mistake the more time you have to fix it – HoskWidsom
The Task – My Mission
The functionality I was going to change on the Incident entity. It was to move a status reason from Resolved to Cancelled.
StateCode and StatusCode
To understand what needed to be done we need to understand Statecode and StatusCode. It’s one of those areas which new CRM developers struggle to understand because statecode and statuscode sound similar, it gets a bit more confusing when the display names are different.
Lets clear it up
StateCode = State
StatusCode = Status Reason
The state/Statecode is the status of the record and holds its current state, most CRM entities have two status = Active/Inactive
Many of the out of box entities have more states. The states are commonly used to control the state of the record and to filter records in views in CRM e.g. Active contacts, Inactive contacts, Completed Tasks, Pending Emails
How is Statuscode linked
One State can have many statuscodes (status reasons) and they work like a linked/Dependant option set.
This article Status and Status Reason values in Dynamics CRM 2013 lists all the out of the box Status and Status Reason’s, I’m guessing this information is probably buried somewhere in the CRM SDK but I couldn’t find it. The two tables below come from the article.
Many entities in CRM have lots of status reasons. Here are the
Status Status Reason
3:Waiting for Details
A few things we know
- Status is the status of a record
- Status Reason is the detailed reason or stage of the current status.
- Status reason allows you to create different stages/reasons for a record. In the case example a case record is created and I view the status reason as to the detailed explanation of status or the stage.
An active case (e.g. case with status of active) can have a status reason of either in progress, On Hold, Waiting for Details, Researching.
Using the status reasons as a filter you can create views/reports and dashboards showing the records in the different stages.
An important point in my story is status and status reasons are special/linked option sets. Option sets in CRM hold two values
- int value
- Text value
The int value is held in the database and the text/string value is held as metadata. I recently blogged about Metadata
If you look at the table of case again you can see the numbers which specify the int value of the Status and Status Reasons
Status and Status Reason act slightly differently from normal option sets. When you add an Option set in CRM it lets you specify the text and the number.
Status Reason doesn’t let you specify the number value
Custom entities have two Status – Active/In Active
For Out of the box entities you cannot add additional Status values.
What I did
I was working on Case (incident schema name) and the task was to remove a status from resolved to cancelled. The status reason (statuscode) was called Case Withdrawn.
I deleted the Status Reason from Resolved
Created a new Status Reason in Canceled to Case Withdrawn
Things quickly got bad, can you guess what mistakes I have made, here are a couple of things to think about
- Code using the case status reasons
- Data in CRM which was set to the Resolved – Case Withdrawn
- How to set it back
The current system I am working on has a lot of customizations. Users cannot directly change the status\Status reason of a case record. Code will be activated by completed tasks or other actions in the system and the status will automatically change.
When I tried to withdraw a case I got an error with this message
961080001 is not a valid status code for state code IncidentState.Resolved on incident with Id 00000000-0000-0000-0000-000000000000
The reason a closeIncidentRequest was being used is because this allows you to specify the values in the IncidentResolution.
If you have closed a case using the CRM GUI, it pops up a form which collects a few values. If my memory serves me correctly, I believe there is an out of the box report which uses the incident resolution values (possibly calculating the time spent via the time on activities)
The change from of the Case Withdrawn status reason meant the code was now trying to set the case to resolved using a status reason from cancelled. This didn’t work.
The reason why the code compiled was because I regenerated the Optionset enums file, so the name of the status reason was the same but the value was different. This value difference would cause me more problems.
The really stupid part of this story is I have done this exact thing before, I did find this problem quicker than last time. The morale of the story is you have to respect the data
When you change a status reason or remove an option set value is you might have a lot of records who have an option set number value which doesn’t exist. You must create a plan to Migrate the data.
Better choices than deleting
Rename the Status record and reuse. Migrate the data as a manual step
rename the status record to DO NOT USE. Migrate the data as a manual step.
It’s important to understand you will need to migrate or at least identify the records which will need migrating before you import and publish your changes.
To find the data with Status reasons pointing to the deleted I had a bit of a tricky problem. I didn’t have access to the SQL Server, so I instead I used
fxb FetchXML Builder created Jonas Rapp
I had been meaning to test it out and it worked very well. To use it all you need to do is drop the dll in your XRMToolBox folder, awesome.
I used this to find the cases which I needed to do something with.
How to set it back
It turned out the bug was a bit bigger than initially anticipated and it was going to be fixed in this release, which mean I had to change everything back.
This proved to be trickier than I originally thought it would be due to the CRM GUI doesn’t let you specify the number for the Status Reason.
I could add in a Status Reason called Case Withdrawn but it would have a completely different int/value number, arghghg.
We were going to restore the database but then I worked out how to add a status reason using the CRM SDK which allow me to specify the number for the status reason.
Stop, think and then act
I often recommend CRM developers should stop and think before they code, this is useful advice before you make any customizations.
When making changes, explain the changes first to your Cardboard developer buddy (Why all developers should be friends with a cardboard developer) and work out the reasons why it might be a bad idea.
A major part of CRM development is avoiding mistakes, missing the gotchas (which is why experience is usually very useful read Why .NET developers struggle with CRM Development to understand why) and this was a classic case of act in haste and take half a day to tidy up CRM.