A colleague recently asked me this question, and I’ll admit that it took me a few minutes to think about the answer. Learning how to write code that extends functionality within or outside of CRM is not something that you can just pick up from scratch. You usually need to have good experience with coding first, before you can safely venture into writing your first plugin or form level JScript function. Fundamental, and arguably, crucial knowledge of the CRM platform is essential too, as this ensures that you don’t put forward solutions that the application can handle natively. In this week’s blog post, I will first clarify what CRM Development actually means, before outlining  my “top tips” on how you can develop your skills to become a superstar CRM Developer.

So what is CRM Development, and is it the same as CRM Customisation?

It’s important that we first clarify what the difference is between these two types of activities, as although there is some cross-over, often they are split out into two distinct roles – a CRM Customiser and CRM Developer. Someone who occupies the first of these roles frequently spends the majority of their time working with CRM Solutions and the Customizations area of CRM. Customisers will commonly be involved in the creation of new entities, fields, system views, processes, business rule & workflows, to name a few. As a consequence, they will more often than not have a great deal knowledge of what the platform is capable of and are generally in the best position to offer support and mentoring to colleagues who are struggling with something in CRM (for example, how to create a personal view).

In comparison, a CRM Developer may spend very little time working with solutions and customisations; although they will be expected to have a general awareness of what the platform is capable of doing,  they will mostly only ever be concerned with modifying plug-ins, plug-in steps and web resources from within solutions. CRM Development instead encompasses a broad canvas of work, all of which is geared towards extending the native functionality of the application. An example list may include:

  • Writing form level JScript, for scenarios where a Business Rule can’t achieve the desired results (we’ve already learned the importance of considering Business Rules as a first step option in these scenarios)
  • Developing custom plug-ins in C#/VB.NET to execute at specific trigger points within the underlying database transaction e.g. after a Contact record has been updated.
  • Building custom workflow assemblies in C#/VB.NET to further enhance the options available as part of a workflow or dialog.
  • Setting up custom Web Resources in HTML/Silverlight, that can be embedded within CRM forms or dashboards.

A good way of remembering the difference is to remember that CRM Customisation can all be done from within the application itself, whereas CRM Development involves work created outside the application to achieve specific business requirements.

Now that we’ve got that out of the way, here’s my top 5 list on how you can learn CRM Development:

Learn C# First

As well as providing you with everything you would ever need from a plug-in/custom workflow development perspective, having a good grasp of C# can make learning JScript a lot more easier. Both languages have a lot of similarity, with some important differences that require noting. First, JScript is largely indifferent when it comes to working with data types, whereas C# is very fussy when it comes to declaring and casting your data types correctly. Secondly, whereas C# development work can be assisted via the use of early-bound class files, JScript can be annoyingly unsympathetic when you write code, with errors only cropping up when you attempt to run your code. Putting aside these differences though, being able to say that you have a good grasp of C# on your C.V. can assist greatly when seeking out roles involving CRM, particularly given such roles will be looking for experience of integrating CRM with third party applications; C# is your Swiss army knife in these situations.

The SDK is your treasure trove.

There are countless number of code examples & snippets enclosed within the SDK, which include all of the languages that you would use to extend CRM – JScript, C# and even VB.NET! These are typically in a state where you can easily deploy them to a test CRM environment, execute them and then playback within Visual Studio via the Plugin Profiler, so you can understand what they are doing.  The enclosed help file (which is replicated fully on the MSDN website) is also really detailed in explaining what you can do when developing for CRM. You can download the latest version of the SDK (updated recently for the 2016 Spring Wave) here.

Get an MSDN Subscription

I have extolled the virtues of what an MSDN subscription can provide to Microsoft professionals previously, so I won’t cover old ground. What I will highlight from this is that the Imagine Academy, included as part of a subscription, contains nearly all of the courses found on the Dynamics Learning Portal (available to CRM partners as a learning resource “hub” for all Dynamics products). It also gives you access to a number of important, developer-focused resources that you add to your arsenal and use to further enhance your knowledge of C#, JScript etc. If you’re fortunate enough to have enough money to obtain an Enterprise MSDN Subscription, or your employer has a few spare licenses, then you will be able to get your hands on a coveted CRM On-Premise license key as well. Working with the application in whatever capacity you can is the best and surest way to learn, as opposed to simply watching videos and reading online articles.

Pass those Exams

There are a wide plethora of different CRM exams available to take currently, and it can be quite confusing deciding which ones will benefit you best on your road to become a CRM Developer. I would suggest that the best exams to target a passing mark on would be the following:

The question you may be asking though is “How important are exams, compared with actual work experience?”. I have heard many debates surrounding the importance of certification, on both sides of the argument; one criticism is that they are generally not a good way equipping candidates with the practical, real-life knowledge and experience that ultimately must come to the fore when working with CRM on a day-to-day basis. Another argument against them is that they can sometimes draw you towards focusing on features of a particular application that is either not very good or is done much better by an alternative product. Notwithstanding this, I think exams hold an important place in demonstrating to colleagues and potential recruiters just how serious you are about your career and in ensuring that you keep yourself up-to-date with the appropriate technology – in these modern times, staying off the ball for as little as a month can put you behind! Going back to the original purpose of this post, the curriculum on both of these exams will leave you in a position where you have achieved a good balance of knowledge: both of what CRM, as a platform, is capable of out of the box, and what you can do to develop further solutions for the application.

And finally, believe in yourself

This last tip may sound a little bit clichéd, but achieving your desire to become good at CRM Development is something that only you have control over. The journey may be hard, and you will often fail more than you succeed at first; but if you keep working at it, never give up and, most importantly, trust in yourself and your abilities, then you will succeed in increasing your knowledge and expertise in CRM.

One of the challenges when first working with CRM Plugins is understanding how the execution context works. To briefly summarise, this contains the information relating to how and when the plugin is executed. It can give you answers to some of the questions you may wish to “ask” your CRM – for example, what record has triggered this plugin? Which CRM User forced the plugin to execute? You may also assume that the context will contain all of the fields of that entity that you can then access. This is not always the case – typically, as part of an Update request, only the fields that triggered the update will be made available. Or consider an Associate/Disassociate request (i.e. when someone relates/unrelates two records in CRM). As part of a one-to-many (1:N) relationship, only one field has changed; so that is the only field that is contained within the execution context.

The above being the case, what options do you have if you need to access fields that are not included in the context? You have two choices available to you:

  1. Obtain a reference to Organisation Service within your plugin, and then use a Retrieve request to return the fields you need from the plugin. As the context will always contain the GUID for the record that has changed, this is generally the most common way you will see this done.
  2. Utilise either a Pre or Post Entity Image of the record, which contains the attributes that you need to access.

In the past, I would always end up going down the first route. Recently though, I have been evaluating Entity Images more and more, and have begun to actively use them as part of my plugins. Entity Images are, essentially, snapshots of the CRM record that are stored on the platform either before the database operation has completed or straight after – you can decide which one you want to use, depending on what your plugin is doing. They are configured as part of deploying your plugin to your target CRM instance, and the steps involved are actually quite simple – I think its generally the case that they are not well-understood or utilised by developers who are learning about CRM for the first time.

So why should you go to the extra effort to use them within your plugins?

As alluded to above, using Pre Entity Images means you can get a snapshot of your CRM data before the data was changed. This may prove to be particularly invaluable in cases where you need to perform a before/after comparison on a particular field, and then execute a specific action either way. The only non-entity image way of doing this within CRM would be via hidden field that stores the current value of your field in question, which is referenced and updated once your business logic has completed. A slightly in-elegant solution, and one that is questionable, given that we can utilise Entity Images instead. Having ready access to the attributes which may not necessarily be exposed to the plugin when it is executed is particularly invaluable, given that this avoids a scenario where you would have to go down option 1). Disadvantages of this approach is the potential for unnecessary delays in your plugin completing and problems with your CRM instance as a whole, if your CRM’s web services are already accessed to breaking point.

There must be a catch, surely…

On the whole, it doesn’t look like it, but you do need to aware of a few things. As you may have already guessed, if your plugin runs on the Create message, you won’t be able to access any pre-image of the record; likewise, for Delete message plugins, the post-image will not be available. It should be fairly obvious why this is the case. You are also restricted with the number of Messages that support Pre/Post Images. The full list can be found here, but to summarise the most important Message types, only Update, Assign, SetState & Merge support both Image types. Bear in mind too the additional “gotchas”, which this article touches upon. If in doubt, then boot up your sandbox CRM environment and cry havoc with your test code.

Lets take a closer look at how Pre and Post Images can be implemented as part of a CRM Plugin…

The below example will compare the Pre and Post Image values of the Lead Company Name field and, if they have changed, send an email message to a Sales Manager user to alert them of this fact. Be sure to add references to the Microsoft.Xrm.Sdk and Microsoft.Crm.Sdk.Proxy .dlls from the SDK:

            //Extract the tracing service for use in debugging sandboxed plug-ins.

            ITracingService tracingService = localContext.TracingService;
            tracingService.Trace("Implemented tracing service succesfully!");

            // Obtain the execution context from the service provider.

            IPluginExecutionContext context = localContext.PluginExecutionContext;

            // Get a reference to the Organization service.

            IOrganizationService service = localContext.OrganizationService;

            if (context.InputParameters.Contains("Target"))

            {
                //Confirm that Target is actually an Entity

                if (context.InputParameters["Target"] is Entity)

                {

                    Guid _userID = context.InitiatingUserId;

                    //Retrieve the name of the user (used later)

                    Entity user = service.Retrieve("systemuser", _userID, new ColumnSet("fullname"));

                    string userName = user.GetAttributeValue<string>("fullname");

                    Entity lead = (Entity)context.InputParameters["Target"];

                    Entity preLead = (Entity)context.PreEntityImages["Image"];

                    Entity postLead = (Entity)context.PostEntityImages["Image"];

                    string preCompanyName = preLead.GetAttributeValue<string>("companyname");

                    string postCompanyName = postLead.GetAttributeValue<string>("companyname");

                    tracingService.Trace("Pre-Company Name: " + preCompanyName + " Post-Company Name: " + postCompanyName);

                    if (preCompanyName != postCompanyName)

                    {
                        tracingService.Trace("Pre-Company Name does not match Post-Company Name, alerting sales manager...");

                        //Queue ID for our Sales Manager

                        Guid _salesManagerQueueID = new Guid("41b22ba9-c866-e611-80c9-00155d02dd0d");

                        Entity fromParty = new Entity("activityparty");
                        Entity toParty = new Entity("activityparty");

                        //Email body text is in HTML

                        string emailBody = "<html lang='en'><head><meta charset='UTF-8'></head><body><p>Hello,</p><p>Please be advised that I have just changed the Company Name of a Lead record in CRM:</p><p>Lead Record URL:  <a href='http://mycrm/MyCrmInstance/main.aspx?etn=lead&pagetype=entityrecord&id=%7B" + lead.Id + "%7D'>" + postCompanyName + "</a></p><p>Old Company Name Value: " + preCompanyName + "</p><p>New Company Name Value: " + postCompanyName + "</p><p>Kind Regards</p><p>" + userName + "</p></body></html>";

                        fromParty["partyid"] = new EntityReference("systemuser", _userID);
                        toParty["partyid"] = new EntityReference("queue", _salesManagerQueueID);

                        Entity email = new Entity("email");

                        email["from"] = new Entity[] { fromParty };
                        email["to"] = new Entity[] { toParty };
                        email["subject"] = "Lead Company Name Changed";
                        email["directioncode"] = true;
                        email["description"] = emailBody;

                        //This bit just creates the e-mail record and gives us the GUID for the new record...

                        Guid _emailID = service.Create(email);

                        tracingService.Trace("Email record " + _emailID + " succesfully created.");

                        //...to actually send it, we need to use SendEmailRequest & SendEmailResponse, using the _emailID to reference the record

                        SendEmailRequest sendEmailreq = new SendEmailRequest
                        {
                            EmailId = _emailID,
                            TrackingToken = "",
                            IssueSend = true

                        };

                        SendEmailResponse sendEmailResp = (SendEmailResponse)service.Execute(sendEmailreq);

                        tracingService.Trace("Email record " + _emailID + " queued succesfully.");
                    }

                    else
                    {
                        
                        tracingService.Trace("Company Name does not appear to have changed, is this correct?");
                        return;
                    }

                    tracingService.Trace("Ending plugin execution.");

Once we have written our code and built our solution, we deploy our plugin in the normal way via the Plugin Registration Tool – ensuring that we configure our step to fire on the correct message:

1

Next, we need to configure our Pre/Post Images manually. Just because we have referenced them in our code above doesn’t mean they will automatically become available to us as our plugin is executed. Fortunately, adding them on is not too difficult and we can do it directly within the Plugin Registration Tool. First, highlight our new plugin step and select Register New Image:

2

One good thing about this is that we can configure both our Pre and Post Images in the one screen. We just confirm that our intended plugin step is selected, tick both of the boxes and ensure that the following details are completed:

  • Name: Specify a name for this image. This can be anything you want it to be, but I would recommend using the same value as Entity Alias
  • Entity Alias: This is the name that is used in our code above to reference the image. This must match exactly against this in order for the code to work correctly.
  • Parameters: Here you specify which attributes will be made available within the image. By default, all attributes are included, but you should specify only the attributes you need to work with in your plugin. For our example plugin, we only need the Company Name field, so this is the only attribute we will select.

Your settings should look something like the below:

3

With everything setup, we can now test our plugin. In this case, I have changed one of the sample data leads to force the plugin to execute:

4 5

We can then see that our Email record is successfully created, which is able to reference the value of the Company Name field before the change:

6

Playing back the plugin within Visual Studio demonstrates that our Pre and Post images are Entity objects, meaning that we can interact with them the usual way – nothing new to learn or master, which is good 🙂

7

Conclusions – or Wot I Think

Like most things with CRM, Entity Images have a time and place. If you have a desire to query additional data concerning record relationship attributes etc. as part of your plugin, then a Retrieve request is still going to be the only way you can accomplish this. There is also some additional administrative head-room required when working with Images – if, for example, you forget to specify your Pre & Post Images when deploying your plugin, then you are going to encounter immediate problems within your CRM. Having said that, I think there is an excellent case to be made to using Entity Images as much as feasibly possible in your code. A Retrieve request on a particular busy CRM platform just to return one attribute value could store up problems for your CRM instance in the long-haul; having this one value stored as part of an entity Image seems a much more sensible and logical approach. I am further struck by how useful entity images can be if you need to reference data changes before and after a record change – going back to our example above, where we would instead look at creating custom field within CRM to store this value, having it made available as part of an Entity Image could score a major time saving. Finally, you are not just restricted to using the one Pre/Post Image – you can setup multiple ones that contain different fields from your entity, that are accessed in different parts of your code, depending on your logic. I think developers working with CRM definitely owe it to themselves to check out Entity Images at least once – I think they will be pleasantly surprised at their capabilities and, one would hope, the increased performance of their CRM instance as a consequence of using them!

When you first begin working with the Dynamics CRM SDK, there is a lot of specific terminology, concepts and methods that need to be grasped firmly. The importance of this is twofold: it enables you to judge the best approach when developing your bespoke solution that fits in within CRM and allows you to gain a very good understanding of the underlying CRM platform. The terms “early-bound” and “late-bound” are, arguably, the most important of these terms/concepts to fully understand, so it is useful to first explain what each of these refer to and the benefits & drawbacks of each, before we get into this weeks blog post properly:

Early Bound

Early bound refers to when you are using a generated class file to access all of the customization data for your CRM within your code. The SDK Bin folder contains an executable file called CrmSvcUtil, which can be used to generate this file. There is a great MSDN article that goes through the steps involved as part of this; once you’ve got the file, simply add it into your Visual Studio project and Intellisense will automatically detect your entity, relationship etc. names! Suffice to say, going down the early bound route can significantly ease your development work, as your strongly-typed class file will contain everything you would require regarding your CRM entities. As such, you will be able to ensure that your code is accessing the correct attributes, relationships and other objects within CRM at all times.

Given that it makes a developers jobs ten times easier, why wouldn’t you want to use Early Bound in your code all the time? Your strongly-typed class file is essentially a snapshot of your current CRM instance, at the date and time when you ran the CrmSvcUtil. As soon as someone makes a change within CRM, your class file may no longer be correct and you may encounter major problems when executing your code against your target CRM environment. This problem can also be compounded if you are developing an ISV solution that is executed against a varied number of environments, where there could be entities/attributes present that your code has no reasonable way to anticipate; in this case, it becomes absolutely essential for you to consider using late-binding instead within your code.

Late Bound

Late binding is the exact opposite of early binding. To access your CRM entities via the late-bound route, you use the Microsoft.Xrm.Sdk.Entity class to declare the new or existing entity that you wish to work with within your code. Typically, you will need to have your CRM Solution open in front of you as part of using late-binding, so that you can reference the logical names of your entities & attributes. You are therefore not “restricted” in the same way that you are with early-binding – you can declare any possible logical name for your CRM objects that you want, which, as mentioned already, is essential if you are blind to the CRM instance(s) in question. There is also a performance benefit to using the late-bound Entiy class, depending on the number of records your code is working with. According to the following Microsoft best practice article:

In addition, if your custom code works with thousands of entity records, use of the Entity class results in slightly better performance than the early-bound entity types.

One of the major drawbacks of using late binding is the increased chance of errors in your code, as you will need to ensure that your logical names are typed correctly; any such mistakes, as we will demonstrate further below, may cause your code to fall over straight away and lead to hours of frustrating debugging in order to resolve.

Now, to the heart of the matter…

I recently had a conversation with a developer colleague, who gave me some advice in relation to some plugin code I had written. Within my code, I had attempted to access the value of a Single Line of Text Entity attribute, using late-bound classes, in the following manner:

string myAttribute = myEntity["myattribute"].ToString();

They recommended instead that I look at using the GetAttributeValue method instead, which in this case, would be expressed in the following manner

string myAttribute = myEntity.GetAttributeValue<string>("myattribute");

My colleague elaborated on a few reasons why this approach is preferential (which I will discuss at the end of this post), but I was interested in dissecting this further myself, to see how it works in practice. In my test CRM instance, I created a plugin for the Contact entity, that would fire on the Post-Operation event of the Update Message, for the First Name field. The plugin very simply accesses the value of this field using the two different approaches, which can be done like so:

//First, initialize your Contact entity

Entity contact = (Entity)context.InputParameters["Target"];

//Now we can access our attributes - the first way is like this...

string contactField1 = contact["firstname"].ToString();

//The second way...

string contactField2 = contact.GetAttributeValue<string>("firstname");

When we play back the plugin execution in Visual Studio, we can see that this returns our expected values. In this particular example, we have attempted to rename our “Homer Simpson” Contact record to “Bart Simpson”:

1 2

 

So, at this stage, there does not appear to be a clear benefit of using one snippet over the other. But what happens if we attempt to access an attribute that does not exist? We can test this by adding the following lines of code to our plugin:

//Next, we test what happens if there is a typo - first, we try GetAttributeValue

string contactField3 = contact.GetAttributeValue<string>("thiswillerror");

//Then, the alternate way

string contactField4 = contact["thiswillerror"].ToString();

When we use our GetAttributeValue approach, our contactField3 returns a null value… :

3

Whereas our contactField4 instantly causes an error, which would also be returned to the user within CRM…

4

Where possible, we always want to try and prevent an Exception from being passed back to CRM and build in the appropriate error handling within our code, and this was one of the benefits that my colleague highlighted in this approach. So, for example, you can build in an if…else statement as part of the above example that checks whether the GetAttributeValue is Null and then perform the appropriate action, depending on the result. Having spent time working with the method more closely, I can also now see a clear benefit in relation to code readability – it is definitely more obvious what GetAttributeValue is doing within a code example, compared with the alternative approach, as well as making it obvious what the data type of the attribute is within CRM. Keep in mind however that you still need to ensure that you are using your correct Data Types, both when creating your C# variables and referencing your CRM attributes – for example, you cannot return the contactid field as a string, as this will generate an InvalidCastException.

To finish off, I would invite you to look through this excellent article from Guido Preite, that takes a deep-dive look at the GetAttributeValue method in a variety of different scenarios. Definitely one to save to your bookmarks 🙂

One of the nice things about working with lookup fields on entity forms is the ability to filter the results programmatically via a form level JScript function. The steps for doing this, thankfully, are rather straightforward:

  1. Prepare a FetchXML filter snippet that applies the type of filtering you want to perform. This can either be written manually, or you can build your filter within Advanced Find, download the FetchXML and then copy + paste the <filter>…</filter> node.
  2. Access the control using the Xrm.Page.getControl object, and then use the addCustomFilter method to apply the filter
  3. Finally, setup your function to fire on the OnLoad event, using the addPreSearch method to actually apply the filter to the lookup control.

If you are still scratching your head at the above, then fortunately there is an MSDN article on the subject which provides further guidance, including a handy example snippet that you can modify and apply straight to your CRM instance. What I like most about this method is that its application is simple, but its potential is quite huge. Utilising conditional logic within your Jscript function means that you could potentially filter results based on values on the form, via a web service request to another CRM record or based on the value in an external CRM/ERP system. Used correctly and prudently, it can help to make form level data inputting much more easier and context sensitive.

CRM has a number of special field types that are reserved for certain system entities. One example is the Customer Data Type field. This is, in essence, a special kind of lookup control that spans two entities – Account & Contact. System customisers are able to create additional fields with this data type, but are unable to create a customer lookup control that spans across 2 entities of choice. This is a real shame, as I think this feature would be really welcome and be widely applicable to a number of different business scenarios.

I was recently working with this particular field on the Contact entity in order to meet a specific requirement as part of a solution – for only certain Account records to appear and for no Contact records to be made available to select. One potential work-around would be to just create a new 1:N relationship between Account:Contact, but this seems rather unnecessary when there is already a relationship in place that could be modified slightly in order to suit requirements. I was therefore interested in finding out whether the addCustomFilter/addPresearch methods could be utilised in this case. After a small, yet sustained, period of severe head-banging, I was able to get this working; although the solution could be argued as being less than ideal.

For the above requirement, lets assume we have a custom field on our Account entity – Company Type – which we are using to indicate what type of UK company an Account is:

4

Once created, we then populate our CRM sample Account records with the value of ‘Limited Company (Ltd.) and run a quick Advanced Find to confirm they return OK:

7

8

Now, for this example, we want to apply a very basic filter on the Customer field on our Contact entity to only show Account records that equal ‘Limited Company (Ltd.)’. Here is our “first attempt” JScript function(s) to achieve this:

function onLoad() {

    Xrm.Page.getControl("parentcustomerid").addPreSearch(modifyCustomerLookupField);

}


function modifyCustomerLookupField() {

	fetchXML = "<filter type='and'><condition attribute='jg_companytype' operator='eq' value='140250000' /></filter>";
	Xrm.Page.getControl("parentcustomerid").addCustomFilter(fetchXML);


}

After creating a JScript Library, adding it to a form, calling the onLoad() function as part of the OnLoad event and, finally (whew!), pressing the magnifying class on the control, we immediately get an error message:

1

Here’s the error message:

5

As a next step, to try and resolve this, I remembered within the FetchXml <filter>…</filter> node, you can specify the name of the entity that you want to filter on. This is typically required when you are using <link-entity> in order to join together multiple records. So I modified my JScript function to include the entity name within the fetchXML filter:

function onLoad() {

    Xrm.Page.getControl("parentcustomerid").addPreSearch(modifyCustomerLookupField);

}


function modifyCustomerLookupField() {

	fetchXML = "<filter type='and'><condition entityname='account' attribute='jg_companytype' operator='eq' value='140250000' /></filter>";
	Xrm.Page.getControl("parentcustomerid").addCustomFilter(fetchXML);


}

With fingers crossed, I then tried again…but still got an error message, but a slightly different one. Some progress at least!

2

3

Once my head had returned from the desk in front of me, I looked back at the original error message, in particular the fact that the Contact entity does not contain the new field we have on our Account. This is to be expected, but is there some way we can “fool” CRM by also having a field with the same name on Contact, but which is not used for anything? Going back into my solution, I created a field with the exact same logical name on the Contact entity, making it clear that this field should not be used. The data type, description etc. does not matter, so long as the logical name is the same as our field above:

6

 

Going back to our form and trying again, we see that this looks to have done the trick – our three records are now returning successfully when we use the control! 🙂

9

Another good thing about this is that the custom filter will also apply when the user clicks on ‘Look Up More Records’; although the Contact entity can still be selected, no records will be returned for obvious reasons.

I was glad this was ultimately possible to get working; however, to play devil’s advocate for a few moments, it is worth noting the following if you choose to set this up within your own environment:

  • Simply creating a new 1:N relationship between Account:Contact would be a more straightforward and less technical approach of achieving the above; you should review your requirements carefully and consider whether this approach would satisfy your objectives.
  • By adding form level JScript to this entity, you may start to impact on your form load times, particularly if you have lots of other functions running on the form or if you implement additional logic into your function.
  • It is arguably not best practice to have a field within CRM that is being used in a “hacky” manner, as outlined above. If you are happy to have such a field within your CRM environment, then you will need to ensure that you take a number of steps to clearly label this field and its purpose within the CRM. The last thing you want is for someone to delete it by accident.

If anyone else has found a better or alternative way of achieving the above, please let me know in the comments below!