Improving Dynamics CRM connection performance with Singleton pattern

Calling Dynamics CRM services is slow process.The authentication process adds the most overhead in establishing a connection to CRM. The call needs to go through multiple steps in order to be authenticated and authorized to access the data in Dynamics CRM.

If you have an application that needs to perform CRUD operation outside of CRM you can leave the connection open to Dynamics CRM with Singleton pattern.

Singleton pattern:

public class XrmConnectionProvider
    {
        private static IOrganizationService instance;
        private static object _lockObject = new object();

		private static IConfigurationService _configurationService;
        
		private XrmConnectionProvider() { }
		
        public static IOrganizationService CRMService
        {
            get
            {
                try
                {
                    lock (_lockObject)
                    {
                        if (instance == null)
                        {
							var container = IoC.Initialize();
							_configurationService = container.GetInstance<IConfigurationService>();


                            instance = Connect();
                        }
                        return instance;
                    }
                }
                catch (Exception ex)
                {
					throw new Exception("Unable to connect to CRM", ex);
                }

            }
        }
        private static IOrganizationService Connect()
        {
		     //user name and password stored in a config file
			var config = _configurationService.Get<XrmClientConfiguration>();

			Uri dInfo = new Uri(config.XrmUri);
			ClientCredentials clientcred = new ClientCredentials();
			clientcred.UserName.UserName = config.XrmClientCredUserName;
			clientcred.UserName.Password = config.XrmClientCredPassword;


			#region on-premise/online

			DiscoveryServiceProxy dsp = new DiscoveryServiceProxy(dInfo, null, clientcred, null);
            dsp.Authenticate();
            RetrieveOrganizationsRequest rosreq = new Microsoft.Xrm.Sdk.Discovery.RetrieveOrganizationsRequest();
            RetrieveOrganizationsResponse r = (RetrieveOrganizationsResponse)dsp.Execute(rosreq);

            //get the OrganizationService
            OrganizationServiceProxy _serviceproxy = new OrganizationServiceProxy(new Uri(config.XrmOrgServiceProxy), null, clientcred, null);
            //In order to use the generated types when dealing with the organization service, you have to add the ProxyTypesBehavior to the endpoint Behaviors collection. This instructs the OrganizationServiceProxy to look in the assembly for classes with certain attributes to use. The generated classes are already attributed with these attributes. Simply, this makes all interactions with the organization service to be done using the generated typed classes for each entity instead of the generic Entity class we used earlier.
            _serviceproxy.ServiceConfiguration.CurrentServiceEndpoint.EndpointBehaviors.Add(new ProxyTypesBehavior());
            //Do not forget to include _serviceproxy.EnableProxyTypes();. Without this line,you will be unable to use early binding.
            _serviceproxy.EnableProxyTypes();
            IOrganizationService service = (IOrganizationService)_serviceproxy;

            #endregion
            return service;
        }
    }

Now in your code you can call below class and it will only generate one instance instead of calling it for each request :

 var CRMService= XrmConnectionProvider.Connect();

Here is the result of URL based test on Visual studio online after applying above approach :

The test ran for duration of 120 seconds with 25 users load hitting my Web API application (hosted on azure) retrieving Dynamics CRM contacts.

Before:

url-based-test-dynamics-crm-before

After:

url-based-test-dynamics-crm-after

 

The only caveat to this method is that you need to monitor your token expiry depending on your implementation method and renew it before expiry.
You can read about it on this blog post.
But before that you need to increase the life time of token if you’ve configured your CRM for IFD.  By default the ADFS token last for 60 minutes. By using Windows PowerShell, you can change the TokenLifetime property for the relying party objects that you created from 60 minutes to a longer period, such as 480 minutes (8 hours).

From MSDN best practices:

Monitor your WCF security token (Token) and refresh it before it expires so that you do not lose the token and have to start over with authentication. To check the token, create a custom class that inherits from the OrganizationServiceProxy or DiscoveryServiceProxy class and that implements the business logic to check the token. Or wrap the proxy classes in a new class. Another technique is to explicitly check the token before each call to the web service. Example code that demonstrates these techniques can be found in the ManagedTokenDiscoveryServiceProxy, ManagedTokenOrganizationServiceProxy, and AutoRefreshSecurityToken classes in the Helper code: ServerConnection class topic.

Advertisements

Merging Entities in Dynamics CRM using Webservices and MergeRequest

If you have lots of duplicated accounts in you system that has lots of records like Orders or Invoices under them, you can use MergeRequest to merge those accounts.

The good thing about merge is that it will handle all relationships for you and you don’t have to worry about related order or invoices ,etc. you also define what fields you want to copy from duplicated record to main record.

Here is a sample code for merging all duplicated accounts in CRM:

I retrieve all accounts in CRM where their account number contains “-0” and then based on that criteria retrieved the duplicated.

If you have more than 5000 records , the Retrieve Multiple will only return first 5000 records so it’s better to use “ConditionOperator” to restrict the number of accounts to be retrieved in your QueryExpression. I find it really useful and fast. Use  “ConditionOperator.Like“…

private void btnMerge_Click(object sender, EventArgs e)
        {
            //connecting to the service and getting the credentials from app.confi
            ClientCredentials Credentials = new ClientCredentials();
            ClientCredentials oClientCredential = new ClientCredentials();
            oClientCredential.UserName.UserName = System.Configuration.ConfigurationManager.AppSettings["userName"];
            oClientCredential.UserName.Password = System.Configuration.ConfigurationManager.AppSettings["password"];

            Uri OrganizationUri = new Uri(System.Configuration.ConfigurationManager.AppSettings["CRMURI"]);
            Uri HomeRealmUri = null;

            using (OrganizationServiceProxy serviceProxy = new OrganizationServiceProxy(OrganizationUri, HomeRealmUri, oClientCredential, null))
            {
                try
                {
                    IOrganizationService service = (IOrganizationService)serviceProxy;

                    QueryExpression query = new QueryExpression();

                    query.EntityName = "account";

                    query.ColumnSet = new ColumnSet() { AllColumns = true };

                    query.Criteria = new FilterExpression();

                    query.Criteria.FilterOperator = LogicalOperator.And;

                    query.Criteria.Conditions.Add

                    (
                     new ConditionExpression("statuscode", ConditionOperator.Equal, 1)//all the active accounts
                    );

                    query.Criteria.Conditions.Add(new ConditionExpression("accountnumber", ConditionOperator.Like, "%-0%"));

                    EntityCollection entities = service.RetrieveMultiple(query);

                    //loop through the list of retrieved entities and find the duplicate that match the criteria 

                    foreach (var item in entities.Entities)
                    {
                        string accountNumber = item.GetAttributeValue<string>("accountnumber").ToString();
                        string mainAccountNumber = item.GetAttributeValue<string>("dtm_mainaccountnumber").ToString();
                        string accountName = item.GetAttributeValue<string>("name").ToString();

                        if (accountNumber.Contains("-0"))
                        {
                            int start = accountNumber.IndexOf('-');
                            string strAdressCode = accountNumber.Substring(start + 1, accountNumber.Length - start - 1);
                            int number;
                            if (Int32.TryParse(strAdressCode, out number))
                            {
                                EntityReference target = new EntityReference();
                                target.Id = item.Id;
                                target.LogicalName = "account";

                                string newAccountNumber = accountNumber.Substring(0, start + 1) + number;

                                QueryExpression innerQuery = new QueryExpression();
                                innerQuery.EntityName = "account";
                                innerQuery.ColumnSet = new ColumnSet() { AllColumns = true };
                                innerQuery.Criteria = new FilterExpression();
                                innerQuery.Criteria.FilterOperator = LogicalOperator.And;
                                innerQuery.Criteria.Conditions.Add(new ConditionExpression("statuscode", ConditionOperator.Equal, 1));
                                innerQuery.Criteria.Conditions.Add(new ConditionExpression("dtm_mainaccountnumber", ConditionOperator.Equal, mainAccountNumber));
                                //innerQuery.Criteria.Conditions.Add(new ConditionExpression("name", ConditionOperator.Equal, accountName));
                                innerQuery.Criteria.Conditions.Add(new ConditionExpression("accountnumber", ConditionOperator.Equal, newAccountNumber));

                                EntityCollection innerEntities = service.RetrieveMultiple(innerQuery);

                                if (innerEntities.Entities.Count >= 1)
                                {

                                    // Create the request for Merge
                                    MergeRequest merge = new MergeRequest();
                                    merge.SubordinateId = innerEntities.Entities[0].Id;
                                    merge.Target = target;
                                    merge.PerformParentingChecks = false;
                                    
                                    // Create another account to hold new data to merge into the entity.
                                    // If you use the subordinate account object, its data will be merged.
                                    Entity updateContent = new Entity("account");
                                    updateContent["dtm_ismerged"] = "merged";

                                    // Set the content you want updated on the merged account
                                    merge.UpdateContent = updateContent;

                                    service.Execute(merge);
                                }
                            }
                            continue;
                        }
                    }
                }
                catch (Exception ex)
                {
                    System.Windows.Forms.MessageBox.Show(ex.ToString());
                    string fileloc = "C:\\myLog.txt";

                    if (File.Exists(fileloc))
                    {
                        using (StreamWriter sw = new StreamWriter(fileloc))
                        {
                            sw.Write(ex.ToString());
                        }
                    }

                }
            }
        }