Category Archives: Power Platform & Dynamics 365 – Server Side Code

Microsoft Dynamics 365: Server Side Bulk Records Operations

In Microsoft Dynamics 365 Customer Engagement server side automation, e.g. plugins and custom workflow activities, we can write code to create, retrieve, update and delete individual records. When we need to carry out these operations on multiple records, we can use a loop and query the database for each record. However, a much more efficient way to carry out bulk operations is to use the ExecuteMultipleRequest message (to Create, Update and Delete records in bulk), the focus on this article. Also, the application example demonstrates how to make use of these bulk operations methods as well as makes use of the RetrieveMultiple Query Expression (i.e. how to retrieve multiple records in single query) .

The bulk operations suite of methods below can be incorporated into a Dynamics 365 project and be called upon to carry out bulk operations. These methods provide an efficient way of carrying out operations on multiple records, thereby enhancing web pages response rates and user experience.

Bulk Create Records

public static void BulkCreateRecords(IOrganizationService service, EntityCollection entityCollection)
{            
	var multipleRequest = new ExecuteMultipleRequest()
	{                 
		Settings = new ExecuteMultipleSettings()
		{
			ContinueOnError = false,
			ReturnResponses = true
		},                
		Requests = new OrganizationRequestCollection()
	};

	foreach (Entity entity in entityCollection.Entities)
	{
		CreateRequest createRequest = new CreateRequest { Target = entity };
		multipleRequest.Requests.Add(createRequest);
	}

	ExecuteMultipleResponse multipleResponse = (ExecuteMultipleResponse)service.Execute(multipleRequest);
}

Bulk Update Records

public static void BulkUpdateRecords(IOrganizationService service, EntityCollection entityCollection)
{           
	var multipleRequest = new ExecuteMultipleRequest()
	{               
		Settings = new ExecuteMultipleSettings()
		{
			ContinueOnError = false,
			ReturnResponses = true
		},               
		Requests = new OrganizationRequestCollection()
	};
   
	foreach (Entity entity in entityCollection.Entities)
	{
		UpdateRequest updateRequest = new UpdateRequest { Target = entity };
		multipleRequest.Requests.Add(updateRequest);
	}

	ExecuteMultipleResponse multipleResponse = (ExecuteMultipleResponse)service.Execute(multipleRequest);
}

Bulk Delete Records

public static void BulkDeleteRecords(IOrganizationService service, EntityCollection entityCollection)
{            
	var multipleRequest = new ExecuteMultipleRequest()
	{ 
		Settings = new ExecuteMultipleSettings()
		{
			ContinueOnError = false,
			ReturnResponses = true
		},                
		Requests = new OrganizationRequestCollection()
	};
   
	foreach (Entity entity in entityCollection.Entities)
	{
		EntityReference entityRef = new EntityReference(entity.LogicalName, entity.Id);

		DeleteRequest deleteRequest = new DeleteRequest { Target = entityRef };
		multipleRequest.Requests.Add(deleteRequest);
	}

	ExecuteMultipleResponse multipleResponse = (ExecuteMultipleResponse)service.Execute(multipleRequest);
}

Example: Calling bulk operations methods and a demonstration of RetrieveMultiple

In the example below, we show how the methods provided above can be put to use to carry out bulk operations, by calling the bulk delete records method, as well as we make use of the RetrieveMultiple Query expression to retrieve multiple records in single database query (see the code below the comment “RetrieveMultiple Query Expression”).

About the plugin

The plugin example below is registered on the Update message Synchronously on the Account entity and PreOperation event pipeline stage of execution. The plugin is triggered by a custom Two Option field called Delete Tasks (logical name: “hos_deletetasks”) field. Therefore, when the user changes the value of the “hos_deletetasks” field from No/false to Yes/true, the plugin retrieves all the tasks that relate to that account and deletes them.

Other things to note

I have added the BulkCreateteRecords, BulkUpdateRecords and BulkDeleteRecords methods to my D365Helpers class, which contains my other reusable methods, so that they can used across multiple server-side automation classes.

using System;
using System.ServiceModel;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;

namespace ItsFascinating.Plugins.D365
{
    public class DeleteFollowUpTasks : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {                  
            if (serviceProvider == null)
            {
                throw new ArgumentNullException("localContext");
            }
            #region Plugin Setup Variables
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
            ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            #endregion
            #region Validation Prior Execution
            string pluginMessageUpdate = "update";           

            if (context.Stage != 20)
            {
                tracingService.Trace("Invalid Stage: Stage = {0}", context.Stage);
                return;
            }
            
            if (!context.MessageName.ToLower().Equals(pluginMessageUpdate))
            {
                tracingService.Trace("Invalid Message: MessageName = {0}", context.MessageName);
                return;
            }
            #endregion
           
            if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
            {
                Entity primaryEntity = context.InputParameters["Target"] as Entity;
                string accountLogicalName = "account";

                if (primaryEntity.LogicalName != accountLogicalName)
                {
                    tracingService.Trace("Invalid Entity: EntityName = {0}", primaryEntity.LogicalName);
                    return;
                }                    

                try
                {
                    if (primaryEntity.LogicalName.Equals(accountLogicalName))
                        DeleteTasks(service, primaryEntity, tracingService);                   
                }                
                catch (FaultException ex)
                {
                    throw new InvalidPluginExecutionException(string.Format("An error occurred in the DeleteFollowUpTasks plug-in. {0}", ex.Message));
                }
                catch (Exception ex)
                {
                    tracingService.Trace(string.Format("DeleteFollowUpTasks plugin: {0}", ex.ToString()));
                    throw new Exception(string.Format("An error occurred in the DeleteFollowUpTasks plug-in. {0}", ex.Message));
                }
            }
        }

        private static void DeleteTasks(IOrganizationService service, Entity primaryEntity, ITracingService tracingService)
        {
            object deleteTasksFieldObj = new object(); ;
            string deleteTasksLogicalName = "hos_deletetasks";
            bool deleteTasks;
            string taskLogicalName = "task";
            string subject = "subject";
            string description = "description";
            string accountRegardingFieldLogicalName = "regardingobjectid";
            bool containsDeleteTasks = primaryEntity.Attributes.TryGetValue(deleteTasksLogicalName, out deleteTasksFieldObj);

            if (containsDeleteTasks)
            {
                deleteTasks = (bool)deleteTasksFieldObj;

                if (deleteTasks)
                {
                    //RetrieveMultiple Query Expression
                    var tasksQuery = new QueryExpression(taskLogicalName);
                    tasksQuery.ColumnSet = new ColumnSet(subject, description);
                    tasksQuery.Criteria.AddCondition(accountRegardingFieldLogicalName, ConditionOperator.Equal, primaryEntity.Id);
                    var tasksResults = service.RetrieveMultiple(tasksQuery);

                    if (tasksResults.Entities.Count > 0)
                        D365Helpers.BulkDeleteRecords(service, tasksResults);
                }
            }
        }
    }
}

Microsoft Dynamics 365: How to Remove Unwanted Special Characters from Text Fields

Microsoft Dynamics 365 has a flexible and extendable architecture, and can be integrated with other platforms. In integrations, it is common for data entered in Dynamics 365 to be shared with other platforms. Although Dynamics 365 text fields are flexible to save special characters, other platforms, that integrate with Dynamics 365, may not be as flexible. For example, when you integrate Dynamics 365 with SharePoint or One Drive, you may want to use the record’s name in Dynamics 365 to create the record’s document set or folder in SharePoint or One Drive . In such a case, Dynamics 365 fields being used by SharePoint or One Drive have to adhere to those platforms’ more restrictive special characters restrictions. The the invalid characters for OneDrive, OneDrive for Business on Office 365, and SharePoint Online are ” * : < > ? / \ | and for OneDrive for Business on SharePoint Server 2013 they are ~ . ” # % & * : < > ? / \ { | }.

The plugin in this post addresses this integration challenge by preventing users from saving restricted special characters in the text field(s) enabled text validation. When creating or updating a record in Dynamics 365, the plugin will check the text field(s), enabled for text validation, and strip out the characters that are not part of the permitted list characters.

To demonstrate this plugin’s functionality, we enabled text validation on the Account Name field for the Account entity. The validation rule applied by the plugin is: the Account Name field is only permitted to take in alphanumeric characters, space and 2 special characters i.e. underscore and hyphen. If a user enters any characters in the field, other than the permitted list of characters, they will be stripped out when they save the record. In the image below, a user entered both valid and invalid characters. Before Save:
Before Saving, user has entered invalid special characters

When saving the record, the plugin stripped out the invalid characters from the text field, leaving only the permitted characters (i.e. alphanumeric characters, space and 2 special characters i.e. underscore and hyphen). After Save:
After Saving, invalid special characters are removed

The Plugin

using System;
using System.Text.RegularExpressions;
using Microsoft.Xrm.Sdk;


namespace ItsFascinating.Plugins.D365
{
    /// 
    /// This plugin removes non-approved special characters from the Account Name field of the Account entity
    /// 
    public class AccountNameCharactersRemover : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            string pluginMessageCreate = "create";
            string pluginMessageUpdate = "update";
            Entity primaryEntity = new Entity();

            if (serviceProvider == null)
            {
                throw new ArgumentNullException("localContext");
            }

            #region Plugin Setup Variables
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
            ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            #endregion
            #region Validation Prior Execution
            //Exist if plugin if it's not called at stage 20 of the excution pipeline
            if (context.Stage != 20)
            {
                tracingService.Trace("Invalid Stage: Stage = {0}", context.Stage);
                return;
            }
           
            //Only execute plugin if plugin message is Create or Update 
            if (!(context.MessageName.ToLower().Equals(pluginMessageCreate) || context.MessageName.ToLower().Equals(pluginMessageUpdate)))
            {
                tracingService.Trace("Invalid Message: MessageName = {0}", context.MessageName);
                return;
            }
            #endregion

            #region Plugin Logic
            if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
            {
                try
                {
                    primaryEntity = context.InputParameters["Target"] as Entity;
                    
                    // Display Name: "Account Name" | Database Name: "name"
                    string accountNameFieldLogicalName = "name";
                    Object accountNameObj;
                    string accountName, updatedAccountName;
                    //Regex pattern for alphanumeric characters, space and 2 special characters i.e. underscore and hyphen                 
                    string permittedCharacters = "[^0-9a-zA-Z_ -]+";
                   
                    //Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
                    if (primaryEntity.Attributes.TryGetValue(accountNameFieldLogicalName, out accountNameObj))
                    {
                        //Get value of that has been entered in the text field
                        accountName = Convert.ToString(accountNameObj);
                        //Remove remove unpermitted characters
                        updatedAccountName = Regex.Replace(accountName, permittedCharacters, "");
                        //Update the value of the text field, after removing the restricted characters
                        primaryEntity.Attributes[accountNameFieldLogicalName] = updatedAccountName;
                    }
                }
                catch (Exception ex)
                {
                    throw new InvalidPluginExecutionException(string.Format("Error occured in the PreUpdateAccount plugin: {0}", ex.Message));
                }
            }
            #endregion
        }
    }
}

Microsoft Dynamics 365: The Complete Guide to Getting and Setting Fields Using a Plugin

When extending the Microsoft Dynamics 365 platform, there is often a need to retrieve and/or set field values. In this post, we will cover how to get and set values for the Microsoft Dynamics 365/CRM platform fields using a plugin, developed using the Microsoft C# programming language. You can also develop plugins and perform the operations demonstrated in this post using other .Net platform-supported programming languages. In Dynamics 365, you can create the following types of fields (or datatypes): Single Line of Text, Option Set, MultiSelect Option Set, Two Options, Image, Whole Number, Floating Point Number, Decimal Number, Currency, Multiple Lines of Text, Date and Time, Lookup and Customer.

Plugin Set Up

Here is a plugin setup you can use to implement the field specific code in this post.

using System;
using System.Collections.Generic;
using Microsoft.Xrm.Sdk;

namespace ItsFascinating_Plugins_D365
{
    public class PreUpdateAccount : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            string pluginMessageUpdate = "update";
            Entity primaryEntity = new Entity();

            if (serviceProvider == null)
            {
                throw new ArgumentNullException("localContext");
            }                

            #region Plugin Setup Variables
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.InitiatingUserId);
            ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            #endregion
            #region Validation Prior Execution
            //Exist if plugin if it's not called at stage 20 of the excution pipeline
            if (context.Stage != 20)
            {
                tracingService.Trace("Invalid Stage: Stage = {0}", context.Stage);
                return;
            }
            //Exist if plugin if it called for any other operation other than Update 
            if (!context.MessageName.ToLower().Equals(pluginMessageUpdate))
            {
                tracingService.Trace("Invalid Message: MessageName = {0}", context.MessageName);
                return;
            }
            #endregion
            #region Plugin Logic
            if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
            {
                try
                {
                    primaryEntity = context.InputParameters["Target"] as Entity;


                    //INSERT THE FIELD SPECIFIC CODE BELOW HERE


                }
                catch(Exception ex)
                {                 
                    throw new InvalidPluginExecutionException(string.Format("Error occured in the PreUpdateAccount plugin: {0}", ex.Message));
                }
                
            }
            #endregion
        }
    }
}               

Field Specific Code

To use the field specific code below, in the plugin above, copy the code below and paste it below the line “//INSERT THE FIELD SPECIFIC CODE BELOW HERE”, in the Plugin Setup Code section.

Single Line of Text

Here is an example of a Single Line of Text field on a Dynamics 365 form:
Single Line of Text

Here is the C# code for getting and setting the value of a Single Line of Text field (Display Name: “Account Number” | Database Name: “accountnumber”):

//Get and Set Single Line of Text field value
// Display Name: "Account Number" | Database Name: "accountnumber"
string accountNumberFieldLogicalName = "accountnumber";
Object accountNumberObj;
string accountNumber;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(accountNumberFieldLogicalName, out accountNumberObj))
{
	//Get value of Single Line of Text field
	accountNumber = Convert.ToString(accountNumberObj);

	//Set value of Single Line of Text field
	primaryEntity[accountNumberFieldLogicalName] = "ACC-1000-TV-2019-ON";
}

Multiple Lines of Text

Here is an example of a Multiple Lines of Text field on a Dynamics 365 form:
Multiple Lines of Text

Here is the C# code for getting and setting the value of a Multiple Lines of Text field (Display Name: “Description” | Database Name: “description”):

//Get and Set Multiple Lines of Text field value
// Display Name: "Description" | Database Name: "description"
string descriptionFieldLogicalName = "description";
Object descriptionObj;
string description;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(descriptionFieldLogicalName, out descriptionObj))
{
	//Get value of Multiple Lines of Text field
	description = Convert.ToString(descriptionObj);

	//Set value of Multiple Lines of Text field
	primaryEntity[descriptionFieldLogicalName] =
		"To be, or not to be, that is the question: \n" +
		"Whether \'tis nobler in the mind to suffer \n" +
		"The slings and arrows of outrageous fortune, \n" +
		"Or to take Arms against a Sea of troubles, \n" +
		"And by opposing end them: to die, to sleep; \n" +
		"No more; and by a sleep, to say we end \n" +
		"The heart-ache, and the thousand natural shocks \n" +
		"That Flesh is heir to? \'Tis a consummation \n" +
		"Devoutly to be wished.To die, to sleep, \n" +
		"perchance to Dream; aye, there\'s the rub, \n" +
		"For in that sleep of death, what dreams may come, \n" +
		"When we have shuffled off this mortal coil, \n" +
		"Must give us pause.";
}

Whole Number

Here is an example of a Whole Number field on a Dynamics 365 form:
Whole Number field

Here is the C# code for getting and setting the value of a Whole Number field (Display Name: “Number of Employees” | Database Name: “numberofemployees”):

//Get and Set Whole Number field value
// Display Name: "Number of Employees" | Database Name: "numberofemployees"
string numberOfEmployeesFieldLogicalName = "numberofemployees";
Object numberOfEmployeesObj;
int numberOfEmployees;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(numberOfEmployeesFieldLogicalName, out numberOfEmployeesObj))
{
	//Get value of Whole Number field
	numberOfEmployees = Convert.ToInt32(numberOfEmployeesObj);

	//Set value of Whole Number field
	primaryEntity[numberOfEmployeesFieldLogicalName] = 120000;
}

Decimal Number

Here is an example of a Decimal Number field on a Dynamics 365 form:
Decimal Number field

Here is the C# code for getting and setting the value of a Decimal Number field (Display Name: “Exchange Rate” | Database Name: “exchangerate”):

//Get and Set Decimal Number field value
// Display Name: "USD/CAD Exchange Rate" | Database Name: "hos_usdcadexchangerate"
string exchangeRateFieldLogicalName = "hos_usdcadexchangerate";
Object exchangeRateObj;
decimal exchangeRate;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(exchangeRateFieldLogicalName, out exchangeRateObj))
{
	//Get value of Decimal Number field
	exchangeRate = Convert.ToDecimal(exchangeRateObj);

	//Set value of Decimal Number field
	primaryEntity[exchangeRateFieldLogicalName] = 1.312775;
}

Floating Point Number

Here are some examples of Floating Number fields on a Dynamics 365 form:
Floating Point Number field

Here is the C# code for getting and setting the value of a Floating Number field (Display Name: “Address 1: Longitude” | Database Name: “address1_longitude”):

//Get and Set Floating Point Number field value
// Display Name: "Address 1: Longitude" | Database Name: "address1_longitude"
string longitudeFieldLogicalName = "address1_longitude";
Object longitudeFieldObj;
float longitudeField;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(longitudeFieldLogicalName, out longitudeFieldObj))
{
	//Get value of Floating Point Number field
	longitudeField = Convert.ToSingle(longitudeFieldObj);

	//Set value of Floating Point Number field
	primaryEntity[longitudeFieldLogicalName] = -79.387054f;
}

Currency

Here is an example of a Currency field on a Dynamics 365 form:
Currency field

Here is the C# code for getting and setting the value of a Currency field (Display Name: “Annual Revenue” | Database Name: “revenue”):

//Get and Set Currency field value
// Display Name: "Annual Revenue" | Database Name: "revenue"
string revenueFieldLogicalName = "revenue";
Object revenueObj;
decimal revenue;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(revenueFieldLogicalName, out revenueObj))
{
 //Get value of Currency field                                               
 revenue = ((Money)revenueObj).Value;

 //Set value of Currency field
 primaryEntity[revenueFieldLogicalName] = new Money(10200500800.78m);
}

Two Options

Here are some examples of Two Options fields on a Dynamics 365 form:
Two Options field

Here is the C# code for getting and setting the value of a Two Options field (Display Name: “Email” | Database Name: “donotemail”):

//Get and Set Two Options field value
// Display Name: "Email" | Database Name: "donotemail"
string dontAllowEmailsFieldLogicalName = "donotemail";
Object dontAllowEmailsObj;
bool dontAllowEmails;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(dontAllowEmailsFieldLogicalName, out dontAllowEmailsObj))
{
	//Get value of Two Options field                                               
	dontAllowEmails = (bool)dontAllowEmailsObj;

	//Set value of Two Options field
	primaryEntity[dontAllowEmailsFieldLogicalName] = true;
}

Option Set

Here is an example of an Option Set field on a Dynamics 365 form:
Option Set field

Here is the C# code for getting and setting the value of an Option Set field (Display Name: “Contact Method” | Database Name: “preferredcontactmethodcode”):

//Get and Set Option Set field value
// Display Name: "Contact Method" | Database Name: "preferredcontactmethodcode"
string contactMethodLogicalName = "preferredcontactmethodcode";
Object contactMethodObj;
int contactMethod;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(contactMethodLogicalName, out contactMethodObj))
{
	//Get value of Option Set field 
	contactMethod = ((OptionSetValue)contactMethodObj).Value;

	//Set value of Option Set field
	primaryEntity[contactMethodLogicalName] = 3;
}

MultiSelect Option Set

Here is an example of a MultiSelect Option Set field on a Dynamics 365 form:
MultiSelect Option Set field

Here is the C# code for getting and setting the values of a MultiSelect Option Set field (Display Name: “Geographical Areas of Operation” | Database Name: “hos_geographicalareasofoperation”) :

//Get and Set MultiSelect Option Set field value
// Display Name: "Geographical Areas of Operation" | Database Name: "hos_geographicalareasofoperation"
string locationFieldLogicalName = "hos_geographicalareasofoperation";
Object locationFieldObj;
OptionSetValueCollection locationField;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(locationFieldLogicalName, out locationFieldObj))
{
	//Get the collection of value(s) for MultiSelect Option Set field
	locationField = (OptionSetValueCollection)locationFieldObj;

	//Set collection of value(s) for MultiSelect Option Set field (version 1 - single line implementation)
	primaryEntity[locationFieldLogicalName] = new OptionSetValueCollection(new List<OptionSetValue>() { new OptionSetValue(183840000), new OptionSetValue(183840001), new OptionSetValue(183840003), new OptionSetValue(183840006), new OptionSetValue(183840010) });

	//Set collection of value(s) for MultiSelect Option Set field (version 2 - multiple lines implementation)
	/*
	OptionSetValueCollection locationMultiOptionSet = new OptionSetValueCollection();
	locationMultiOptionSet.Add(new OptionSetValue(183840000));
	locationMultiOptionSet.Add(new OptionSetValue(183840001));
	locationMultiOptionSet.Add(new OptionSetValue(183840003));
	locationMultiOptionSet.Add(new OptionSetValue(183840006));
	locationMultiOptionSet.Add(new OptionSetValue(183840010));
	primaryEntity[locationFieldLogicalName] = new OptionSetValueCollection(locationMultiOptionSet);
	*/
}

Date and Time

Here are some examples of Date and Time fields on a Dynamics 365 form:
Date and Time field

Here is the C# code for getting and setting the value of a Date and Time field (Display Name: “Follow Up Date” | Database Name: “hos_followupdate”):

//Get and Set Date and Time field value
// Display Name: "Subscription Start" | Database Name: "hos_subscriptionstart"
// Display Name: "Subscription End" | Database Name: "hos_subscriptionend"
string subscriptionStartLogicalName = "hos_subscriptionstart";
string subscriptionEndLogicalName = "hos_subscriptionend";
Object subscriptionStartdObj;
DateTime subscriptionStart = new DateTime();

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(subscriptionStartLogicalName, out subscriptionStartdObj))
{
	if (subscriptionStartdObj != null)
	{
		//Get value of Date and Time field  
		subscriptionStart = Convert.ToDateTime(subscriptionStartdObj);

		//Set value of Date and Time field   
		primaryEntity[subscriptionEndLogicalName] = subscriptionStart.AddYears(1);
	}
	else
	{
		//If the Subscription Start is null, set the Subscription End as well
		primaryEntity[subscriptionEndLogicalName] = null;
	}
}

Lookup

Here is an example of a Lookup field on a Dynamics 365 form:
Lookup field

Here is the C# code for getting and setting the value of a Lookup field (Display Name: “Account Manager” | Database Name: “hos_accountmanager”) :

//Get and Set Lookup field value
// Display Name: "Account Manager" | Database Name: "hos_accountmanager"
string accountManagerLogicalName = "hos_accountmanager";
Object accountManagerObj;
EntityReference accountManager;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(accountManagerLogicalName, out accountManagerObj))
{
	if (accountManagerObj != null)
	{
		//Get value of a Lookup field and its attributes                                              
		accountManager = (EntityReference)accountManagerObj;
		Guid guid = accountManager.Id;
		string entityName = accountManager.LogicalName;
		string name = accountManager.Name;

		//Set value of a Lookup field and its attributes                        
		primaryEntity[accountManagerLogicalName] = new EntityReference("systemuser", new Guid("A6AF4DAB-10C4-E911-A2E2-005056AE4389"));
	}
}

Customer

Here is an example of a Customer field on a Dynamics 365 form:
Customer field

Here is the C# code for getting and setting the value of a Customer field (Display Name: “Main Customer” | Database Name: “hos_maincustomer”):

//Get and Set Customer field value
// Display Name: "Main Customer" | Database Name: "hos_maincustomer"
string mainCustomerLogicalName = "hos_maincustomer";
Object mainCustomerObj;
EntityReference mainCustomer;

//Check if the specified attribute is contained in the internal dictionary before you you try to Get its value
if (primaryEntity.Attributes.TryGetValue(mainCustomerLogicalName, out mainCustomerObj))
{
	if (mainCustomerObj != null)
	{
		//Get value of Customer field and its attributes                                              
		mainCustomer = (EntityReference)mainCustomerObj;
		Guid guid = mainCustomer.Id;
		string entityName = mainCustomer.LogicalName;
		string name = mainCustomer.Name;

		//Set value of Customer field and its attributes                        
		primaryEntity[mainCustomerLogicalName] = new EntityReference("account", new Guid("2E4D98F9-8EF9-E911-A2E8-005056AE4389"));
	}
}

Extra Details

The plugin demonstrated in this post was developed on Microsoft Dynamics 365 version 9.0.3.7 and Microsoft Dynamics 365 SDK version 9.0.2.12.

Optimize Entity Attributes Access in Dynamics 365

I was recently working at a client site and came across the error/exception below. A plugin I had developed a few weeks earlier stopped working in the integration environment after an update to a different class/plugin. The error did not make sense and sent me on a wild goose chase for a few hours. How can code function in one environment and not in another environment, when one environment is a replicate of the other and all the parameters as well as infrastructure is the same? As a software developer, the favorite part of my job is coming across such conundrums.

The Error

“The given key was not present in the dictionary” exception

This was as much information as I could get from the system. This exception indicates that I was trying to access an attribute that was not present in the internal .NET dictionary. I looked very closely at both environments and they had all the entity attributes I was accessing in the plugin.  

In investigating this error, I came across a more efficient way to access entity attributes in Microsoft Dynamics 365. Often, before Dynamics 365 software developers perform logic on a retrieved value, they have to check if the retrieved value exists. Executing such logic usually follows the following path or something similar (we use the Contact entity in this example but this can be applied to any out of the box entity or custom entity):

Accessing attributes using Attributes.Contains() method

//Declare the contact entity and retrieve it from the execution context
Entity contact = pluginExecutionContext.InputParameters["Target"] as Entity;
//Check if attribute exists in collection, using Attributes.Contains
if (contact.Attributes.Contains("company"))
{
    //Get attribute of interest, as a string
    string contactCompany = contact.GetAttributeValue("company");
    //Perform operations on your attribute of interest
    /*
    */
}

Accessing attributes using Attributes.TryGetValue() method

//Declare the contact entity and retrieve it from the execution context
 Entity contact = pluginExecutionContext.InputParameters["Target"] as Entity;
 //Object that will store the attribute object if the retrieval is successful
 Object contactComp;
 //Check if attribute exists in collection, using Attributes.TryGetValue
 if (contact.Attributes.TryGetValue("company", out contactComp))
 {
    //Get attribute of interest, as a string
    string contactCompany = (String)contactComp; 
     //Perform operations on your attribute of interest
     /*
     */
 } 

Despite the Attributes.Contains() method and the Attributes.TryGetValue() being used to achieve the same objective, the latter is more efficient. When you use the Attributes.Contains() method, your code is performing two look up operations in the background. First, it checks if the specified attribute is contained in the internal dictionary. If the dictionary contains the specified attribute, another look up will be performed to get the corresponding value for the attribute. The Attributes.TryGetValue() method performs both operations in one swoop by returning a Boolean value, true (value exists) or false (value does not exist), and an output object, which contains the attribute value.

If you are only accessing the attribute dictionary as shown above, a few times, you may not see significant savings in execution time, by using Attributes.TryGetValue(). However, as the application grows and you are performing these operations regularly or in a loop, the code optimizations benefits of using Attributes.TryGetValue() become self-evident.

To learn more about Attributes.TryGetValue() method, look at the Microsoft documentation: TryGetValue().

Conclusion

The Attributes.TryGetValue() method resolved the exception I was getting within my plugin and optimized my code. This is more than what I had bargained for when I began my investigation, which is a terrific and extremely rewarding outcome. Therefore, I would recommend the Attributes.TryGetValue() method over the Attributes.Contains() method.