In the world of Dynamics CRM/Dynamics 365 for Enterprise (D365E), the start of the year generally means the introduction of new exams in line with the latest version of the product. 2017 is no exception to this rule and, at the time of writing, there are 4 new D365E exams that you can start to get your teeth into:

Microsoft appears to be drip feeding the current wave of exams this time around: the first exam to popup was MB2-716 at the start of February, with the remaining exams cropping up over the last week or so. What’s also worth noting is that the current exam list for D365E is only viewable via the US Microsoft Learning site; if you are UK based like me, then the current Dynamics certification page makes no mention of the new exams…yet. I seem to remember this being a problem last year as well and, like back then, you can still book your exam and sit it in your country of choice by simply going through the US Microsoft Learning Website.

Exams present a good opportunity to re-familiarise yourself with areas of a particular product that you have not had much exposure to previously, as well as introducing you to anything new that has been introduced over the past year. In this week’s blog post, I will take a closer look at the new exams and the differences that new and previous candidates should make note of before preparing to revise.

Customer Organizational Structure: What It Is and Why You Shouldn’t Worry

Both the MB20715 and MB2-717 dedicate a significant percentage of exam performance on a candidate’s ability to ‘Create a Customer Organizational Structure‘. Exam veterans may initially be put off by this terminology, as it is not something that has ever been referenced previously. Upon closer inspection of both exams, the skills measured differs, compounding any potential confusion. Fortunately, the top-level terminology is more confusing than what is underneath. To simplify things for those who may be still scratching their heads, here is a breakdown for each exam of what you will need to focus on:

  • MB2-715
    • Support the Microsoft Dynamics 365 client environment: This covers things such as knowing which browsers are compatible with D365E, which mobile devices/operating systems that the mobile app support and also minimum software/hardware requirements for the D365E App for Outlook (Note: this is NOT the same as the Dynamics 365 for Outlook).
    • Deploy Microsoft Dynamics: This will likely cover what license types are available, what permissions they grant across the application and also what features you get as part of a subscription. For example, remember that subscriptions now include a free sandbox and 10GB database storage.
    • Import Data into the Microsoft Dynamics 365 Environment: This will cover the Data Import Wizard and all its subtle nuances, as well as the new Data Loader service (surprising, given that it is still in preview apparently).
    • Manage the Microsoft Dynamics 365 Environment: This is likely to cover all of the Office 365 “soft skills” that are required as part of managing D365E Online and, rather interestingly, Single Sign-On (SSO) via Active Directory Federation Services (ADFS) – something that has only ever come up as part of On-Premise exams previously.
  • MB2-717
    • Manage Customer Records: This will include topics covering your “basic” record types and how they behave (Accounts, Contacts etc.), as well as having to demonstrate knowledge of Business Units and how to structure the application to match a business hierarchy.
    • Manage the Sales Process: This will cover your full sales qualification process – Lead to Opportunity to Quote to Order to Invoice – and how these record types interact, the unique behaviours of each and potentially some stuff covering Business Process Flows.
    • Manage Customer Communication: Same as the above, this will be focused towards knowledge of Opportunity and Lead records. There may also be a sneaky question or 2 about Social Engagement chucked in, based on the terminology used.
    • Manage sales literature and competitors: This will include working with document templates, the Competitor record type and potentially some questions regarding Connections and Connection Roles.

So on balance, nothing too scary as part of the above for those who have sat previous exams. That’s why it’s always important to dig deeper behind a headline to get the true story underneath!

And It’s Goodnight From Me: Saying Farewell to the On-Premise Exam

One notable absentee from the list of new exams is the On-Premise Installation exam. The previous exam for 2016, MB2-711, demonstrates a candidate’s proficiency in installing and administrating the On-Premise version of Dynamics 2016; something which, based solely on my own experience managing an on-premise lab environment, is no small feat. Now it very much looks if this exam has gone the way of the Dodo. As highlighted by legendary CRM/D365E MVP Mark Smith, there is currently no content on the Dynamics Learning Portal/Imagine Academy that covers On-Premise installation of D365E.

Although the retirement of this exam type (if true) does come with some drawbacks for those who may be tasked with supporting on-premise versions of the application in the near future, it is perhaps not surprising. The key thing that Microsoft have been trying to highlight as part of the D365E release is the clear benefit of the cloud version of the product over its companion, self-hosted versions. This is why Microsoft have been offering incentivised upgrade pathways, sprinkled with a generous helping of price reductions, to motivate organisations to move to the Online version of the product. Whilst On-Premise D365E will continue to have a role to play in the months and years ahead – which is why Microsoft offer Dual Use Rights with online subscriptions (see below) – its role will be relegated to merely providing organisations with an offline mechanism for deploying development/test environments within their own infrastructure.

Excerpt from the Dynamics 365 Enterprise Edition Licensing Guide. Click on the image to download the full guide.

With regards to some of the topic areas covered by the former On-Premise Installation exam – such as Server-Side-Synchronisation and CRM for Outlook – you can be assured/annoyed at the fact that these topics are picked up within the new MB2-715 exam instead. So don’t take these subjects too lightly when revising 🙂

Missed Opportunities

As we welcome the new exams and what they can offer, they also present an opportunity to evaluate what is missing and what could be improved upon in the future. With this in mind, here are a few things that are a shame to be have been missed as part of this wave of exam releases:

  • With the retirement of MB2-701: Extending Microsoft Dynamics CRM 2013 at the end of the last year, the death knell was signalled for Developer CRM/D365E certifications. With no current exam on the horizon to replace MB2-701, this presents a major missed opportunity. Familiar readers of the blog will know that I have railed against this in the past, chiefly for the reason is that it creates a lack of incentivisation for existing functional CRM consultants or developers new to the product to take a dive and learn what is possible via the platform through coding. I hope that this is eventually addressed and that we see an Extending Dynamics 365 exam or similar released in future.
  • I did a post last year discussing the possible imminence of a CRM Portal exam, based on evidence garnered from the Adxstudio website. CRM Portals is such a huge product in of itself, that presents its own unique blend of learning curves and challenges when coming from a purely CRM-focused background. Having an exam dedicated solely to this presents, in my view, the surest way pathway for those interested in implementing the product as part of future projects to get running with it. This being the case, it is a shame that a Portal Exam has not yet been included as part of the above list.
  • I did hear some rumours last year that Microsoft was planning on “resetting” the current state of affairs regarding CRM/D365E exams and their status within the Microsoft certification hierarchy. Unlike the “big hitters” in the Microsoft range of products, such as Azure and Office 365, which have MCSA/MCSE level qualifications, CRM/D365E have continually been relegated to Microsoft Specialist level for each of the exams passed; something which, I have to admit, does not look as snazzy on your C.V. 🙁 I was hoping that with the love and attention shown to CRM last year as part of the D365E rebranding, that we would see a brand new D365E MCSA released. Perhaps this may happen in the future, as I believe this is one of the ways that Microsoft can clearly signal the importance of D365E moving forward.

Conclusions or Wot I Think

I have yet to sit any of the new exams, although it is something that I am tentatively planning for over the next couple of months. It will be interesting to see how the experience differs compared to previous exams, if at all. Despite the rebranding, the content of these exams feels to be very safe on balance; i.e. the structure is largely identical compared to their equivalent 2016 version, with some slight peppering of new content to cover some of the muted new features within the product. Some new features appear to have been left out altogether – for example, there is no specific mention of some of the new Process updates or even the new built-in Sitemap editor. I would hope that these exams are a stop-gap for a completely new range of exams that will be released in the future, that place the D365E application front and centre with the other much-loved favourites in the Microsoft “family.” This would also have the added benefit of providing candidates with the opportunity to more clearly specialise within non-traditional areas of the application.

For those who are well versed in rolling out solution updates within Dynamics CRM/365 for Enterprise (CRM/D365E), the process will always have a certain familiarity to it, with a few surprises rolled in now and again. Often, the update will proceed as anticipated; sometimes, you may encounter bizarre issues. I can remember a particularly strange incident I had last year where a solution import would get to about 90-95% completion…and then the green progress bar would suddenly start rolling back to nothing. The import progress window would then hang with no further guidance or error message. To try and determine the root cause, we had to interrogate the importjob entity within the system, which ended up showing the import job progress stuck at 0.15846281% : / In the end, we had to escalate the issue to Microsoft for further investigation, but rest assured that if you have not yet witnessed your own curious solution import experience, it’s definitely in the post 🙂

Thankfully, if you are new to the whole “rolling out solution update” thing, you can be assured that the process is relatively straightforward, and mostly without issue. If you have been handed a set of solution import instructions for the first time, though, you may be wondering why something similar to the following step is included:

Go into the Data Management -> Duplicate Detection Rules page and click Publish on all Duplicate Detection Rules that have a Status Reason of Unpublished

Unfortunately, after importing a solution update, CRM/D365E will automatically unpublish all of your Duplicate Detection Rules automatically. You are therefore required to explicitly publish them again, lest you start to encounter a sudden increase in duplicate records and database storage within your system. The reason why this happens is both understandable and frustrating in equal measure. As outlined in the following MSDN article on the subject:

A duplicate rule condition specifies the name of a base attribute and the name of a matching attribute. For example, specify an account as a base entity and a contact as a matching entity to compare last names and addresses

As part of the above, explicit matchcodes are created for every record that the Duplicate Detection Rule is targeting, based on the current metadata of your CRM/D365E entities and attributes. Because your solution update can potentially alter significant aspects of this metadata, the system automatically unpublishes all Duplicate Detection Rules as a precaution.

The above is perhaps trivial in nature, as the actual process of re-publishing all Duplicate Detection Rules is somewhat negligible in effort terms. Where difficulties can arise is if someone innocently overlooks this part of the process or if your system has many different Duplicate Detection Rules, in a mixture of Unpublished/Published state. You would have to specifically make a note of which rules were Published before beginning your solution import so that you can ensure that the correct rules are published after the fact. I would have thought that after so many versions of the product, that something would be added to address this – for example, perhaps a checkbox at the start of the Solution Import Wizard that lets you specify whether all currently published rules should be reactivated after the import completes successfully.

If you find that the above is an annoyance that you can do without no longer, like with many things on the platform, there is a solution that can be deployed in code. The SDK exposes the PublishDuplicateRuleRequest class, which does exactly what it says on the tin – meaning that you can write a plugin that applies this functionality accordingly. The tricky bit comes in determining which Message (i.e. action) on the platform that you wish to run this against. CRM/D365E does not expose a SolutionUpdate or SolutionImport message that we can piggy-back onto, so we have to look at the PublishAll message instead – the action that is triggered when you press Publish All Customizations in the system. This is because this is generally the action you will always need to take when importing an (unmanaged) solution. As a result, we can write a plugin class that is triggered on the Post-Operation event of this entity to automatically publish all Unpublished Duplicate Detection Rules in the system!

The snippet below is adapted from the sample code provided by Microsoft, but has been tweaked as follows:

  • A QueryExpression is used as opposed to QueryByAttribute, since we need to query on two separate attributes and their values – statecode and statuscode. You also cannot return an easily accessible count on all results returned with QueryByAttribute. We will see why is useful in a few moments.
  • The code explicitly checks for if there are any Unpublished rules first before attempting to proceed further – no point in running code unnecessarily!
  • Instead of activating each rule one-by-one using an Execute request, all of the requests are collected together as part of an ExecuteMultipleRequest, given that we now know the performance benefits that this can have.
  • Tracing has been implemented in liberal amounts, to provide remote debugging from within CRM/D365E.

Here’s the code – just copy into an empty class file on your plugin project, modify the namespace to reflect the name of your project and you will be good to go!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Crm.Sdk.Messages;

namespace MyPlugin.Plugins
{
    public class PostPublishAll_PublishDuplicateDetectionRules : IPlugin 
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            //Obtain the execution context from the service provider.

            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

            //Get a reference to the Organization service.

            IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = factory.CreateOrganizationService(context.UserId);

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

            ITracingService tracing = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

            tracing.Trace("Tracing implemented successfully!");

            if (context.MessageName == "PublishAll")

            {
                PublishRules(service, tracing);
            }
        }

        private void PublishRules(IOrganizationService service, ITracingService tracing)

        {
            EntityCollection rules = GetDuplicateDetectionRules(service);

            tracing.Trace("Obtained " + rules.TotalRecordCount.ToString() + " duplicate detection rules.");

            if (rules.TotalRecordCount >= 1)

            {
                // Create an ExecuteMultipleRequest object.
                ExecuteMultipleRequest request = new ExecuteMultipleRequest()
                {
                    // Assign settings that define execution behavior: don't continue on error, don't return responses. 
                    Settings = new ExecuteMultipleSettings()
                    {
                        ContinueOnError = false,
                        ReturnResponses = false
                    },
                    // Create an empty organization request collection.
                    Requests = new OrganizationRequestCollection()
                };

                //Create a collection of PublishDuplicateRuleRequests, and execute them in one batch

                foreach(Entity entity in rules.Entities)

                {

                    PublishDuplicateRuleRequest publishReq = new PublishDuplicateRuleRequest { DuplicateRuleId = entity.Id };
                    request.Requests.Add(publishReq);
                    
                }

                service.Execute(request);

            }

            else

            {
                tracing.Trace("Plugin execution cancelled, as there are no duplicate detection rules to publish.");
                return;
            }
        }

        private EntityCollection GetDuplicateDetectionRules(IOrganizationService service)

        {
            QueryExpression qe = new QueryExpression("duplicaterule");

            qe.ColumnSet = new ColumnSet("duplicateruleid");

            ConditionExpression condition1 = new ConditionExpression();
            condition1.AttributeName = "statecode";
            condition1.Operator = ConditionOperator.Equal;
            condition1.Values.Add(0);

            ConditionExpression condition2 = new ConditionExpression();
            condition2.AttributeName = "statuscode";
            condition2.Operator = ConditionOperator.Equal;
            condition2.Values.Add(0);

            FilterExpression filter = new FilterExpression();
            filter.FilterOperator = LogicalOperator.And;
            filter.Conditions.Add(condition1);
            filter.Conditions.Add(condition2);

            qe.Criteria.AddFilter(filter);

            //Have to add this, otherwise the record count won't be returned correctly

            qe.PageInfo.ReturnTotalRecordCount = true;

            return service.RetrieveMultiple(qe);

        } 
    }
}

The only caveat with the above is that it is arguably only useful for if you are regularly importing Unmanaged, as opposed to Managed solutions, as the Publish All Customizations option is not displayed on the import wizard for unmanaged solutions. Nevertheless, by rolling out the above into your environment, you no longer need to scrabble around for the mental note you have to make when performing a solution update 🙂

It is often the case, as part of any application or database system, that certain record types will be well-suited towards duplication. Whilst this is generally a big no-no for individual customer records or invoice details, for example, there are other situations where the ability to duplicate and slightly modify an existing record becomes incredibly desirable. This is then expanded further to the point where end-users are given the ability to perform such duplication themselves.

A good example of this can be found within Dynamics CRM/Dynamics 365 for Enterprise (CRM/D365E). Email Templates are, in essence, a record type that is duplicated whenever a user selects the Template and creates a new Email record from within the application. Whilst there will always be details that need to be modified once the duplication is performed, having the ability to essentially “copy + paste” an existing record can generate the following benefits for a business:

  • Streamlining and adherence to business processes
  • Efficiency savings
  • Brand consistency

CRM/D365E does a pretty good job of making available a number of record types designed solely for this purpose, but a recent real-life example demonstrated a potential gap. A business I was working with was implementing the full sales process within the application – Lead to Opportunity to Quote etc. At the Quote stage, the businesses existing process would generally involve having a number of predefined “templates” for Quotes. This was due to the fact that the business would very regularly quote for the same kind of work, often with little or no variation. Out of the box with CRM/D365E, the sales team would have to create a new Quote record and add on every Quote Product line item each time a Quote was required – leading to little or no efficiency benefit of using the application.

To get around the issue, I was tasked with creating a means of setting up a number of “template” Quote records and then have the ability to quickly copy these template records, along with all of their associated Quote Product records, with some minor details changed in the process (for example, the Name value of the Quote). A workflow is immediately the best candidate for addressing the second requirement of this task but would require some additional development work to bring to fruition. I decided then to look, rather nervously, at creating a custom workflow assembly.

Why nervously? To be frank, although I have had plenty experience to date with writing plugins for CRM/D365E, I had not previously developed a custom workflow assembly. So I was a little concerned that the learning curve involved would be steep and take much longer than first anticipated. Fortunately, my fears were unfounded, and I was able to grasp the differences between a plugin and a custom workflow assembly very quickly:

  • Instead of inheriting from the IPlugin interface, your class instead needs to be set to the CodeActivity interface. As with plugins and, depending on Visual Studio version, you can then use CTRL + . to implement your Execute method.
  • Context (i.e. the information regarding the who, what and why of the execution; the User, the Entity and the action) is derived from the IWorkflowContext as opposed to the IPluginExecutionContext
  • Input/Output Parameters are specified within your Execute method and can be given a label, a target entity and then information regarding the data type that will be passed in/out. For example, to specify an Input Parameter for a Quote EntityReference, with the label Quote Record to Copy, you can use the following snippet:
[Input("Quote Record to Copy")]
[ReferenceTarget("quote")]
public InArgument<EntityReference> QuoteReference { get; set; }

The rest is as you would expect when writing a C# plugin. It is good to know that the jump across from plugins to custom workflow assemblies is not too large, so I would encourage anyone to try writing one if they haven’t done so already.

Back to the task at hand…

I implemented the appropriate logic within the custom workflow assembly to first create the Quote, using a Retrieve request to populate the quote variable with the Entity details and fields to copy over:

Entity newQuote = quote;
newQuote.Id = Guid.Empty;
newQuote.Attributes.Remove("quoteid");
newQuote.Attributes["name"] = "Copy of " + newQuote.GetAttributeValue<string>("quotenumber");
Guid newQuoteID = service.Create(newQuote);

The important thing to remember with this is that you must set the ID of the record to blank and then remove it from the newQuote – otherwise, your code will attempt to create the new record with the existing GUID of the copied record, resulting in an error.

Next, I performed a RetrieveMultiple request based off a QueryExpression to return all Quote Product records related to the existing records. Once I had my results in my qp EntityCollection, I then implemented my logic as follows:

foreach (Entity product in qp.Entities)

    {
        Entity newProduct = product;
        newProduct.Id = Guid.Empty;
        newProduct.Attributes.Remove("quotedetailid");
        newProduct.Attributes["quoteid"] = new EntityReference("quote", newQuoteID);
        service.Create(newProduct);
    }

After deploying to CRM and setting up the corresponding Workflow that referenced the assembly, I began testing. I noticed that the Workflow would occasionally fail on certain Quote records, with the following error message:

The plug-in execution failed because the operation has timed-out at the Sandbox Client.System.TimeoutException

The word Sandbox immediately made me think back to some of the key differences between CRM/D365E Online and On-Premise version, precisely the following detail pertaining to custom code deployed to Online versions of the application – it must always be deployed in Sandbox mode which, by default, only allows your code to process for 2 minutes maximum. If it exceeds this, the plugin/workflow will immediately fail and throw back the error message above. Upon closer investigation, the error was only being thrown for Quote records that had a lot of Quote Products assigned to them. I made the assumption that the reason why the workflow was taking longer than 2 minutes is because my code was performing a Create request into CRM for every Quote Product record and, as part of this, only proceeding to the next record once a success/failure response was returned from the application.

The challenge was therefore to find an alternative means of creating the Quote Product records without leading the Workflow to fail. After doing some research, I came across a useful MSDN article and code example that utilised the ExecuteMultipleRequest message:

You can use the ExecuteMultipleRequest message to support higher throughput bulk message passing scenarios in Microsoft Dynamics 365 (online & on-premises), particularly in the case of Microsoft Dynamics 365 (online) where Internet latency can be the largest limiting factor. ExecuteMultipleRequest accepts an input collection of message Requests, executes each of the message requests in the order they appear in the input collection, and optionally returns a collection of Responses containing each message’s response or the error that occurred.

Source: https://msdn.microsoft.com/en-us/library/jj863631.aspx

Throwing caution to the wind, I repurposed my code as follows, in this instance choosing not to return a response for each request:

// Create an ExecuteMultipleRequest object.

ExecuteMultipleRequest request = new ExecuteMultipleRequest()

{
    // Assign settings that define execution behavior: continue on error, return responses. 
    Settings = new ExecuteMultipleSettings()
    {
        ContinueOnError = false,
        ReturnResponses = false
    },
    // Create an empty organization request collection.
    Requests = new OrganizationRequestCollection()
};

    foreach (Entity product in qp.Entities)

    {
        Entity newProduct = product;
        newProduct.Id = Guid.Empty;
        newProduct.Attributes.Remove("quotedetailid");
        newProduct.Attributes["quoteid"] = new EntityReference("quote", newQuoteID);
        CreateRequest cr = new CreateRequest { Target = newProduct };
        request.Requests.Add(cr);

    }

service.Execute(request);

Thankfully, after re-testing, we no longer encountered the same errors on our particularly large Quote test records.

As a learning experience, the above has been very useful in showcasing how straightforward custom workflow assemblies are when coming from a primarily plugin development background. In addition, the above has also presented an alternative method for creating batch records within CRM/D365E, in a way that will not cause severe performance detriment. I was surprised, however, that there is no out of the box means of quickly copying existing records, thereby requiring an approach using code to resolve. Quotes are an excellent example of an Entity that could benefit from Template-isation in the near future, in order to expedite common order scenarios and help prevent carpel tunnel syndrome from CRM users the world over 🙂

Organisations that deploy Dynamics CRM/Dynamics 365 for Enterprise (CRM/D365E) can immediately take advantage of a number of inbuilt functionality, processes and data models that can be re-purposed with minimal effort. Whilst this approach can often lead to more streamlined deployment of your CRM/D365E solution, individuals customising the system should take care not to make the system fit around a business too much; rather, the opposite must be achieved where ever possible and careful analysis should be carried out in the outset to ensure that this balance is maintained. Sacrificing sensible business processes to accommodate for the quirks of a particular business system is a major pitfall that should be avoided as part of any major IT system deployment.

A good example of this can be found in the pricing calculation engine within CRM/D365E, which is utilised by the following entities within the system:

  • Opportunity
  • Opportunity Product
  • Quote
  • Quote Product
  • Order
  • Order Product
  • Invoice
  • Invoice Product

Rather than having to implement your own logic to generate prices for these entities, businesses can choose to utilise the pricing engine to automatically generate the net total for your Products, calculate appropriate Discounts and then figure out the final total at the end.

For those who are dissatisfied with how CRM performs this calculation, you will be pleased to hear that you have the option to override the default pricing engine and specify your own via a C# plugin. More information, and a very handy code example, can be found on our good friend MSDN:

The pricing engine in Microsoft Dynamics 365 supports a standard set of pricing and discounting methods, which might be limiting to your business depending on your specific requirements for applying taxation, discounts, and other pricing rules for your products. If you want to define custom pricing for your products in opportunities, quotes, orders and invoices, you can use the CalculatePrice message.

To use the custom pricing for your opportunities, quotes, orders, and invoices:

  1. Set the value of the Organization.OOBPriceCalculationEnabled attribute to 0 (false). You can also use the Sales tab in the system settings area in Microsoft Dynamics 365 or Microsoft Dynamics 365 for Outlook to disable system pricing. More information:  Configure product catalog information
  2. Create a plug-in that contains your custom pricing code for calculating the price for your opportunity, quote, order, or invoice.
  3. Register the plug-in on the CalculatePrice message.

Source: https://msdn.microsoft.com/en-us/library/dn817885.aspx

I think the most key thing as part of the above is not to overlook the simplest step – namely, modifying the setting within CRM/D365E that lets you specify your custom pricing engine in the first place. If this is not set, then you may spend many hours trying to figure out why your beautifully developed plugin is not working! It can be found very straightforwardly in Administration area of the application, on the System Settings page:

Whilst the code example provided by Microsoft gives you a good flavour of what you can potentially achieve with your own custom logic, I thought I would share two further examples that I recently was involved in developing, which may also prove useful when putting together your own custom pricing engine.

Calculating Custom Fields/Attributes

Arguably one of the biggest benefits of implementing your own custom pricing engine is being able to incorporate additional fields as part of the calculation. A recent real life example best demonstrates this. I was implementing a quoting solution for a business within Dynamics CRM 2015. The organisation was, fortunately, able to utilise much of the out of the box functionality within CRM as part of their existing processes. The only caveat was that they wanted the ability to add a Margin value at the Order level, in a similar vein to the Discount fields currently on the Quote entity – a Discount value and a Discount percentage value. The organisation wanted the option to do both, either or neither i.e. have the ability to specify a Margin value AND an additional percentage on top of that.

After configuring the appropriate fields within CRM to store both a currency value for the Margin and a decimal value for the Margin (%), we then proceeded to write some custom code that would achieve this aim. A snippet of this can be found below, which takes an existing total value of all Products on a Quote and then applies the correct calculation. It is worth explaining that the system returns NULL values if there is no data in the field when using the GetAttributeValue method (a fact I was already well aware of), which is why we have to specifically set the variables with a default value of 0 and perform the NULL check:

decimal margin = 0;
decimal marginPercent = 0;

if (quote.GetAttributeValue<Money>("new_mycustommarginamountfield") != null)
    {
        margin = quote.GetAttributeValue<Money>("new_mycustommarginamountfield").Value;
    }

if (quote.GetAttributeValue<Money>("new_mycustommarginpercentagefield"))
    {
        marginPercent = quote.GetAttributeValue<decimal>("new_mycustommarginpercentagefield");
    }

//Calculate margin amount based on the total amount

total = total + margin;
quote["totalamountlessfreight"] = new Money(total);
service.Update(quote);

//Calculate margin percentage based on the total amount

decimal marginPercentVal = marginPercent / 100 * total;
total = total + marginPercentVal;
quote["totalamountlessfreight"] = new Money(total);
service.Update(quote);

Calculating Sales Tax

CRM/D365E makes the assumption that tax will always be calculated on the Product Detail level. That’s why the Quote Product, Opportunity Product, Order Product and Invoice Product entities have a Tax field, demonstrated below on the Quote Product form:

There are a few problems with this, however:

  • You cannot set a default Tax for each Product in the system. What this means is that you have to drill down to every Product details entity and populate the Tax manually. Whilst you could look at a Business Rule, Workflow or some custom code to get around this issue, this seems like a rather complicated solution to something that you would expect to be easy to configure.
  • My experience indicates that most companies in the UK calculates tax on the gross amount of an order, and not at an individual Product level. Attempting to try and change a common practice to fit around a business system is a good example of what I spoke about in the introduction to this blog post.
  • Generally, most organisations will work with a flat rate of Tax for all Products (unless they are dealing with other countries). With this in mind, it seems a little crazy having to set this on an individual basis.

By using our own custom calculation logic, we can get around the above and implement a solution that best meets our need. For example, here is a code snippet that will take the total value of all Products on the Order entity and then calculate the VAT tax amount at 20%, saving the tax-only amount and the Net Total back to the system:

decimal vat = 0.20m;
decimal total = 0;
decimal tax - 0;

for (int i = 0; i < ec.Entities.Count; i++)
    {
        total = total + ((decimal)ec.Entities[i]["quantity"] * ((Money)ec.Entities[i]["priceperunit"]).Value);
        (ec.Entities[i])["extendedamount"] = new Money(((decimal)ec.Entities[i]["quantity"] * ((Money)ec.Entities[i]["priceperunit"]).Value));
        service.Update(ec.Entities[i]);
    }

//Calculate total from tax

tax = total * vat;
total = total + tax;
order["totaltax"] = new Money(tax);
order["totalamount"] = new Money(total);                
service.Update(quote);

It would be disingenuous of me not to point out that the above solution has its own faults – the biggest one being that your code would require manually updating if the tax rate ever changes in the future. You can perhaps get around this issue by instead storing the current tax rate within a CRM entity, that can be updated in line with any future changes. Your plugin could then query this entity/attribute each time the plugin is executed.

Conclusions or Wot I Think

Whilst the ability to override a key feature within CRM/D365E is incredibly welcome and a great example of how you can leverage the system without compromising on your existing business processes, it is arguable that this process is hardly straightforward. A passing knowledge of C# is mandatory to even begin to start implementing your own custom pricing engine, as well as good awareness of how CRM/D365E plugins work. This is all stuff that an administrator of the application may struggle to grasp, thereby requiring dedicated resource or knowledge within the business to implement the desired solution. It would be nice to perhaps see, as part of a future version of D365E, the ability to specify a custom pricing engine within the application itself – similar to how Business Rules were introduced and reduced the need for form-level JScript functions to achieve common tasks. Nevertheless, it has been good to discover that the CalculatePricing message is exposed within the application and that the application has the flexibility for end users to modify (and perhaps improve upon 🙂 ) one of its key features.

One of the dangers when working as part of a specific role within a technology-focused occupation is that a full 360-degree knowledge of an application, and some its more subtle nuances, can often be lacking. This is particularly true for those who work with Dynamics CRM/Dynamics 365 for Enterprise (CRM/D365E). For example, most people can say with confidence how an entity is created but may be less familiar with the process of removing an entity and the list of things you need to watch out for as part of this process. This can be exacerbated if your role is involved primarily with “build a solution” projects, where often you will build out components and hand it over to a customer; it is unlikely that, after this point, you will have much involvement on how the solution shapes, evolves and also (arguably) some of the challenges that can be faced when working with the application on a daily basis. This is, I would argue, essential experience and an area that should be addressed if you feel it is lacking in some way.

I encountered a strange issue recently when performing a tidy-up of an existing Dynamics CRM 2016 solution. The work involved “breaking up” the solution into more logical groupings, based on business functions. After getting these solution components archived into an unmanaged solution, we then proceeded to delete the components from the primary solution file, one by one.

Deleting solution components can often be a laborious process, thanks to the applications Dependent Components feature, which prevents you from deleting in-use components from the application. Whilst no doubt a highly beneficial feature to have in place, it can prove to be difficult to unwrap in practice. Assuming your team/business is following sound principals relating to change management, as I have argued for previously on the blog, all CRM Administrators/Customisers should have some experience of doing this. For those who have yet to take the plunge, though, it’s important to remember the following:

  • Dependent Components include pretty much everything that you can find with a Solution file, so you won’t need to look further afield in order to track down any components.
  • Relationships can be the trickiest component to remove dependencies for, as the Lookup field on the related entity will need to be removed from all forms and views first.
  • Certain components may need to be deactivated first before they can be safely deleted. For example, Workflows and Business Process Flows.

I definitely prefer CRM/D365E having this feature in place, but it can feel like a double-edged sword at times.

Going back to the task at hand, we were close to getting all the entities that needed deleting completed, but we encountered the following issue when deleting an entity:

The Component Type and Dependency Type fields were the only fields populated with information – Sdk Message Processing Step and Published respectively –  so we were initially left stumped at what the issue could be. We did some digging around within CRM to see what the problem is, by first of all querying Advanced Find to return all of the Sdk Message Processing Steps records for the entity concerned. There were two records that caught our attention:

Both records do not have a Name field populated, just like the Dependent Components highlighted above, and also contain the following useful Description value – Real-time Workflow execution task. This would immediately suggest that the issue relates to a Workflow of some description. But we had already deactivated/deleted all workflows that reference the Entity or its Attributes.

After some further research and a good night sleep, I came back to the issue and remembered some obscure information relating to Business Rules from MSDN and the how they are stored in CRM:

The following table describes relevant process trigger entity attributes.

SchemaName Type Description
ControlName String Name of the attribute that a change event is registered for. For other events this value is null.
ControlType Picklist Type of the control to which this trigger is bound.

The only valid value for this release is 1. This indicates that the control is an attribute. This value only applies when the ControlName is not null.

Event String There are three valid values to indicate the event:

  • load
  • change
  • save
FormId Lookup ID of the form associated with the business rule.

This value is null when the rule applies to all forms for the entity that supports business rules.

IsCustomizable ManagedProperty Information that specifies whether this component can be customized.

You cannot change process trigger records included in a managed solution when the IsCustomizable.Value is false.

PrimaryEntityTypeCode EntityName Logical name for the entity that the business rule is applied on.
ProcessId Lookup ID of the process.
ProcessTriggerId Uniqueidentifier ID of the process trigger record.

From the applications point of view, it appears that Business Rules are treated the same as the more typical Processes. This theory is backed up by the fact that Business Rules have to be explicitly Activated/Deactivated, just like a Workflow, Action or other types of process. After going back to the Entity to double-check, we confirmed that there were indeed two Active Business Rules configured; and, by deleting them and checking Dependent Components again, we were safely able to the delete the Entity.

When attempting to reproduce this issue within a test environment, later on, I was able to clarify that the issue does not occur all of the time. From the looks of it, both of the Entities that we were attempting to delete the above had a relationship and the Business Rules in question were directly referencing the Lookup field. So, when reproducing the issue with a standard Business Rule configured (i.e. not referencing any lookup field), I was able to delete the entity successfully. So it is good to know that it is a rare issue and one that will not be commonplace whenever you need to delete an entity. Nevertheless, this issue demonstrates clearly the importance of familiarising yourself regularly with scenarios with CRM/D365E that you are not generally exposed to, within a testing environment or similar. Doing this will almost certainly throw up a few things that you can learn at the end of it and better equip yourself for any problems you may face in the future.