Multiple entities in Microsoft Dynamics CRM

Multiple entities in Microsoft Dynamics CRM

Over the past few years, Microsoft Dynamics CRM has gained popularity as a powerful tool for solving complex enterprise tasks. Developers often rely on plug-ins, dialogues, and workflows to implement various scenarios. However, these out-of-the-box tools sometimes fall short in providing the needed functionality.

In this article, we’ll explore using dialogues for multiple entities. We’ll demonstrate this by tackling a task where we have customers with invoices. The goal is to increase the invoice discount by a certain amount for all selected customers.

A standard workflow approach within a dialogue isn’t feasible here. Retrieving references to customers’ invoices would be difficult, and the process would need to be repeated for each customer. 

Custom workflow activities could be an option, but entering procedure parameters, such as the discount increase amount, would be challenging. Using only JavaScript might lead to issues with transaction management and procedure integrity.

To address this, we’ll combine different Dynamics CRM coding approaches. Specifically, we’ll pair JavaScript with plug-ins functionality. Microsoft Dynamics CRM allows developers to extend and modify the CRM system using .NET assemblies, known as plug-ins. These plug-ins can be triggered by events such as entity creation, modification, or deletion.

Our solution involves using JavaScript to gather primary data, such as selected customers. We’ll use dialogues to collect additional data, like the amount to adjust the discount. Plug-ins will then process all the data. We’ll create a .NET assembly and register it in Dynamics CRM to enhance the system’s standard logic, ensuring a smooth and efficient process for this task.

First of all, let’s look at the concept:

  1. For ribbon editing (creating a button for action) we’ll be using Ribbon Workbench.
  2. To collect the selected customers IDs and pass them to the dialog and plug-in we will use custom entities, which will store IDs of customers and one parent entity for them to store data entered in the dialog. Entities with IDs will be connected to the parent entity.
  3. Dialogue will have these actions:
    • prompt user to enter required values;
    • update parent entity with data entered by the user via dialogue (the update will trigger plug-in execution).
  4. Plug-in will read the data from parent entities and associated entities with customer IDs and process customers’ invoices. Usage of C# language in the plug-in will give us an opportunity to create a complex logic and integrate it into CRM. CRM 2011 SDK provides .NET the assemblies needed to work with the data stored in any entity.

Preparing CRM entities

So, first things first: let’s create new entities for processing and add existing required entities to a solution. To do this, go to solution components, add a new entity and fill the form as shown in Picture 1 (other entity options are not important right now). For testing purposes, we will display this entity in the Settings Area.

Child Entity creation form
Picture 1 – Child Entity creation form

Add these fields to our new entity:

  • Contact
    • Name: ar_contact
    • Type: Lookup
    • Target Record Type: Contact
  • Parent Entity
    • Name: ar_parententity
    • Type: Lookup
    • Target Record Type: Parent Entity

Now do the same for the parent entity:

Parent Entity creation form
Picture 2 – Parent Entity creation form

Fields:

  • Amount:
    • Name: ar_amount
    • Type: Currency

Note that entity names can be different: prefix can differentiate in different solutions.

Also, to create a button for customers, add a Contact entity to the solution.

Creating dialogue for user interaction

The next step is to create a dialogue for Parent entity for user interaction with our process. Go to solution components and add new Process. Fill the form with data provided in Picture 3 and press OK.

Dialog creation form
Picture 3 – Dialog creation form

You should see the Dialog designer form:

Dialog designer form
Picture 4 – Dialog designer form

Click the Select this row and click Add Step field and add a new Page step:

Adding a new page
Picture 5 – Adding a new page

Select the row in the page step, add the new step Prompt and Response and click Set Properties:

Creating Prompt and Response step
Picture 6a – Creating Prompt and Response step
Creating Prompt and Response step 2
Picture 6b – Creating Prompt and Response step

Fill in the form as shown below, then save and close it:

Prompt and Response designer form
Picture 7 – Prompt and Response designer form

In the Process creation window select Page step and add the new step — Update Record, then select Set Properties:

Adding Update Record
Picture 8 – Adding Update Record

Now we need to fill in the Amount field. To do this, place the cursor in the Amount field by clicking on the textbox and then select Prompt for an amount in Look for dropdown and Response text in the dropdown below, click the Add button and then OK in Form Assistant pane. You should see the form shown in the picture below with the filled Amount field. Press the Save and Close button.

Update Record designer form
Picture 9 – Update Record designer form

OK, dialogue steps are prepared now. To publish the dialogue press the Activate button at the top of the page and confirm activation in the Confirmation dialogue. The dialogue is ready to use.

Creating JavaScript code for starting a dialogue

The next step will be to write a JavaScript library to create a Parent entity, list of Child entities, attach them to the Parent entity, and then open the dialogue that we have created.

The first thing we need is to get dialogue ID to start it over CRM API. This is how you can obtain it: go to Parent Entity view, select one record (create some with any data, if none exists), click Start Dialog button in ribbon panel and select the Process Customers dialogue.

Starting dialogue manually
Picture 10 – Starting dialogue manually

We will use CRM REST Endpoint with JavaScript. More info on using REST Endpoint can be found here (documentation) and here (samples). SDK library (SDK.JQuery.js) can be found at or in the SDK installation folder, at SampleCode\ JS\RESTEndpoint\ JQueryRESTDataOperations (without spaces).

Here’s the JavaScript code (process.js):

var dialogId = "38D394F3-0C1B-46C5-A682-53F373501CBD";
var parentEntityLogicalName = "ar_parententity";

function Process(items) {
    if (items.length != 0) {
        var parentEntity = {};
        var parentEntityId;
        var parentEntityName;

        // Creating parent entity with some name
        parentEntity.ar_name = (new Date()).toString();
        SDK.JQuery.createRecord(
            parentEntity,
            parentEntityLogicalName,
            // call back from parent entity creating
            function (entity) {
                // saving parent entity id and name
                parentEntityId = entity.ar_parententityId;
                parentEntityName = entity.ar_name;

                // creating child entities
                for (var i = 0; i < items.length; i++) {
                    var childEntity = {};
                    childEntity.ar_name = (new Date()).toString();
                    // setting ParentEntity field to assotiate child entity with parent
                    childEntity.ar_ParentEntity = { Id: parentEntityId, LogicalName: parentEntityLogicalName, Name: parentEntityName };
                    // storing customer in child entity using the reference to customer
                    childEntity.ar_Customer = { Id: items[i].toString(), LogicalName: parentEntityLogicalName };
                    // creating record
                    SDK.JQuery.createRecord(
                        childEntity,
                        "ar_childentity",
                        function(chEntity) {},
                        errorHandler
                    );
                }

                // we didn't wait until all children will be created so starting dialog immediately after parent entity is created
                var serverUri = Mscrm.CrmUri.create('/cs/dialog/rundialog.aspx');
                window.showModalDialog(serverUri + '?DialogId=' + "%7b" + dialogId + "%7d" + '&EntityName=' + parentEntityLogicalName + '&ObjectId=' + parentEntityId,
                                null, 'dialogWidth: 615px; dialogHeight: 480px; resizable: yes; status: yes, scrollbars:yes;');
            },
            errorHandler
        );
    } else {
        alert("No records selected");
    }
}

function errorHandler(error) {
    alert(error.message);
}

Publishing JavaScript

To make the provided JavaScript code sample work some additional libraries have to be uploaded to CRM and published:

  1. jQuery.js
  2. json2.js
  3. restSDK.js

All of them can be found in the CRM 2011 SDK samples code or in the solution provided. Don’t forget to upload our library and name it process.js.

After libraries upload the Web Resources tab should look as in picture 11 below:

Web Resources view
Picture 11 – Web Resources view

Creating a button

Before creating a button ensure that Contact entity is included in your solution and install the Ribbon Workbench solution on your CRM system. Open the application by clicking the Ribbon Workbench button in the Customizations menu. Select your solution and click OK. Workbench will load the solution and show all the entities that belong to it with their ribbon panels.

In the Entities panel select contact from the Entities column, then in Solution elements right-click Commands and click Add New: a new command will be created.

Adding a new command to the Contact entity
Picture 12 – Adding a new command to the Contact entity

Now we need to add our JavaScript function to this command. Right-click on command and select Edit Actions. In a dialogue click Add, select Javascript Function Action and click OK. In the right panel type in the FunctionName, which in our case will be Process. Select library, Process library in our case. Open the Parameters dialogue by clicking the button located near Parameters textbox.

Adding JavaScript function to command
Picture 13 – Adding JavaScript function to command

Click Add, in a dialogue that opens select Crm Parameter. Name it items (naming is optional). In the Value dropdown select SelectedControlSelectedItemIds and click OK to close the Parameters dialogue.

Adding parameters to the function
Picture 14 – Adding parameters to the function

Now let’s load other libraries into the Contact view. There are different ways to do that (see here). В We’ll just add the scripts to actions on button click. In the Actions dialogue add new Javascript Function Action with FunctionName $ and jQuery library without any parameters, then do the same for JSON library, and then once again for Rest SDK with FunctionName isNaN. Use Up and Down Arrows to move jQuery, JSON and SDK libraries higher in the list and load them before the execution of our script. After that your Actions window should look like this (Picture 15):

Adding additional libraries
Picture 15 – Adding additional libraries

Click OK to save added actions.

Next step is to create a button: drag and drop the button item from Toolbox to any place you want (we placed it in the Process group after Start Dialog button), make sure you have selected the contact entity and Homepage from the top right dropdown menu. Fill the LabelText field in button properties and select the command that we have created earlier.

Creating a new button
Picture 16 – Creating a new button

After that click Publish Solution and wait until the process completes. Now you can go to Customers->Contacts page, select contacts and click the new Process Invoices button (refresh the page if it doesn’t show up). This will create one Parent Entity with some Child Entities and start the Dialog.

Creating and publishing the plug-in

Now we can create a plug-in which will fire on Parent Entity Update and process the customers’ invoices.

Create a new plug-in project in Visual Studio. Generate the data model of your CRM organization using CrmSvcUtil.exe (see here). Use the following command line with your credentials and CRM URL:

CrmSvcUtil /url:https://article0808.crm4.dynamics.com/XRMServices/2011/Organization.svc /out:Xrm.cs /username:”[email protected]” /password:”adminadmin” /namespace:Xrm /serviceContextName:PluginServiceContext

Add the generated file to the project.

Create a new ProcessCustomersPlugin class and inherit it from Plugin class.

Here’s code from this class:

using System;
using System.Linq;
using System.Text;

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;

using Xrm;

namespace ProcessMultipleRecords
{
    /// <summary>
    /// Process customers' invoices plugin
    /// </summary>
    public class ProcessCustomersPlugin : Plugin
    {
        /// <summary>
        /// Post operation pipeline stage.
        /// </summary>
        private const int PostOperationPipelineStage = 40;

        /// <summary>
        /// Create event name.
        /// </summary>
        private const string UpdateEventName = "Update";

        /// <summary>
        /// Assign customers to billing run trigger CRM name.
        /// </summary>
        private const string ParentEntityLogicalName = "ar_parententity";

        /// <summary>
        /// Initializes a new instance of the <see cref="ProcessCustomersPlugin"/> class.
        /// </summary>
        public ProcessCustomersPlugin()
            : base(typeof(ProcessCustomersPlugin))
        {
            this.RegisteredEvents.Add(
                new Tuple<int, string, string, Action<LocalPluginContext>>(
                    PostOperationPipelineStage, UpdateEventName, ParentEntityLogicalName, this.ExecuteProcessCustomers));
        }

        /// <summary>
        /// Executes customers processing.
        /// </summary>
        /// <param name="localPluginContext">
        /// The local plugin context.
        /// </param>
        private void ExecuteProcessCustomers(LocalPluginContext localPluginContext)
        {
            if (localPluginContext == null)
            {
                throw new ArgumentNullException("localPluginContext");
            }

            // Getting trigger entity
            ar_parententity trigger;
            if (localPluginContext.PluginExecutionContext.InputParameters != null
                && localPluginContext.PluginExecutionContext.InputParameters.Contains("Target")
                && localPluginContext.PluginExecutionContext.InputParameters["Target"] is Entity)
            {
                var entity = (Entity)localPluginContext.PluginExecutionContext.InputParameters["Target"];
                if (!entity.LogicalName.Equals(ar_parententity.EntityLogicalName))
                {
                    throw new ArgumentException("Plugin execution failure. Can not retrieve required reading.");
                }

                trigger = entity.ToEntity<ar_parententity>();
            }
            else
            {
                throw new ArgumentException("Plugin execution failure. Can not retrieve required reading.");
            }

            this.ProcessCustomers(trigger, localPluginContext);
        }

        /// <summary>
        /// Processes invoces connected to customers.
        /// </summary>
        /// <param name="trigger">
        /// The trigger with amount value.
        /// </param>
        /// <param name="localPluginContext">
        /// The local plugin context.
        /// </param>
        private void ProcessCustomers(ar_parententity trigger, LocalPluginContext localPluginContext)
        {
            using (var context = new PluginServiceContext(localPluginContext.OrganizationService))
            {
                // Getting Child Entities
                var triggerChildrens = context.ar_childentitySet.Where(x => x.ar_ParentEntity.Id == trigger.Id);
                foreach (var triggerChildren in triggerChildrens)
                {
                    // Getting customers from Child Entitites
                    var customer = context.ContactSet.FirstOrDefault(x => x.Id == triggerChildren.ar_Customer.Id);
                    if (customer != null)
                    {
                        // Getting invoices
                        var invoices =
                            context.InvoiceSet.Where(
                                x => x.CustomerId.Id == customer.Id && x.StateCode == InvoiceState.Active);
                        foreach (var invoice in invoices)
                        {
                            // Updating invoices
                            invoice.DiscountAmount =
                                new Money(
                                    (invoice.DiscountAmount ?? new Money(0)).Value
                                    + (trigger.ar_Amount ?? new Money(0)).Value);
                            context.UpdateObject(invoice);
                        }
                    }
                }

                // Saving all changes
                var results = context.SaveChanges(SaveChangesOptions.ContinueOnError);
                if (results.HasError)
                {
                    var sb = new StringBuilder();
                    foreach (var result in results)
                    {
                        if (result.Error != null)
                        {
                            sb.AppendFormat("{0}\n", result.Error.Message);
                        }
                    }

                    throw new InvalidOperationException(sb.ToString());
                }
            }
        }
    }
}

Sign the assembly with a key file and build it. Now we can register plug-in using the Plug-in Registration Tool. Go to your Developer Tool Kit installation folder, then to sdk\bin and run pluginregistration executable. Click the Create New Connection button, fill in the form and click Connect. For more info about discovery, see URL.