Publish a Duplicate Detection Rule: Behind the scenes

When you import a solution you get a nice progress bar that you can use to approximately estimate the finish time. But when you publish a duplicate detection rule, you get nothing. All you can basically do is hit the publish and check back 10-15 minutes later.

Sometimes, it might sit in the publishing state longer that you expect. It can be frustating to just wait, without actually knowing whether the process has frozen or it is actually doing something. The underlying entity DuplicateRule is not searcheable from advanced find, and so there is no other information available except the window saying “Publishing”.

I did some research into what actually happens under the covers when you hit publish. When you hit publish CRM has to:

  1. Create new matchcode tables for the base entity and the matching entity
  2. Populate the matchcode tables with the duplicate criteria specified in the rule

For some reason that I haven’t been able to figure out so far, it seems the matchcode table count is always actual entity count + 1. There is a matchcode record with ObjectId 00000000-0000-0000-0000-000000000000 in each matchcode table. I have no idea why this record exists, but my theory is that this is some sort of bit mask. I maybe totally wrong on this though and this could just be some sort of sequence.

I wrote the script below to confirm the duplicate rules are indeed doing something.

declare @duplicaterules table (rownum int identity(1,1), name varchar(200),matchcodetable varchar(200),entity varchar(200))
declare @i int,@matchcodetable varchar(200),@entity varchar(200),@name varchar(200),@max int
declare @result varchar(max),@query nvarchar(max),@matchcodecount int,@entityrowcount int
insert into @duplicaterules
select name,MatchingEntityMatchCodeTable,MatchingEntityName from DuplicateRule where statecode=0
select @max=@@ROWCOUNT,@i=1
while(@i<=@max)
begin
	select @matchcodetable=matchcodetable,@entity=entity,@name=name from @duplicaterules where rownum=@i
	set @query=N'select @matchcodecountOUT=count(*) from ' + @matchcodetable + ' where ObjectId<>''00000000-0000-0000-0000-000000000000'''
	exec sp_executesql @query,N'@matchcodecountOUT int OUTPUT',@matchcodecountOUT=@matchcodecount OUTPUT
	set @query=N'select @entityrowcountOUT=count(*) from ' + @entity
	exec sp_executesql @query,N'@entityrowcountOUT int OUTPUT',@entityrowcountOUT=@entityrowcount OUTPUT
	print 'Duplicate Rule '+@name+' is '+CAST(CAST(@matchcodecount*100.0/@entityrowcount as numeric(5,2)) as varchar(6))+'% complete: '
	print 'Entity: '+@entity+', Matchcode Table: '+@matchcodetable+', Matchcodes: '+CAST(@matchcodecount as varchar(20))+', Entity Rows: '+CAST(@entityrowcount as varchar(20))+CHAR(10)
	set @i=@i+1
end

When you run the script in SSMS on the MSCRM database, you will see a output that looks something like this.

DuplicateOutput

So now you can quickly check the status in the database, if you are little impatient. Once again, sorry CRMOnline, but I like having access to the actual database.

EDIT (22/09/15): Updated SQL Script slightly to produce the percentages correctly. The previous script was always displaying integer. I also exclude matchcode row with objectid 00000000-0000-0000-0000-000000000000

SQL Script: Entity row count

Run the script below on the MSCRM database to quickly get a count of records, for real and customisable entities (not logical).

declare @query nvarchar(max)
set @query=''
select @query=@query+'select count(*) Records,'''+Label+''' Entity, '+ cast(IsCustomEntity as varchar(1)) +' as IsCustomEntity from '+name+' union all ' from dbo.LocalizedLabelAsIfPublishedView a
inner join dbo.EntityAsIfPublishedLogicalView b on a.ObjectId=b.EntityId
where a.ObjectColumnName='LocalizedName'
and IsLogicalEntity=0 and IsCustomizable=1
set @query='select Entity,Records, case when IsCustomEntity=''0'' then ''No'' else ''Yes'' end as CustomEntity from ('+left(@query,len(@query)-10)+') s where s.Records>0 order by Entity'
exec sp_executesql @query

Quicktip: Always retrieve primary key with distinct=’true’ fetchxml

FetchXml is great when you quickly want to extract some data. Today, I found a weird behaviour, that is either a bug, or a documented “feature” that I am not aware of.

If you run this code below

            var fetch = @"
            <fetch version='1.0' output-format='xml-platform' mapping='logical' {0} count='1'>
	            <entity name='contact'>
		            <attribute name='fullname'/>
	            </entity>
            </fetch>";

            Console.WriteLine("With Distinct True And Without Id\n--------------------------------");
            var results = crmSvc.OrganizationServiceProxy.RetrieveMultiple(new FetchExpression(string.Format(fetch, "distinct='true'"))).Entities.ToList();
            results.ForEach(x => Console.WriteLine("Id: {0}",x.Id));

            Console.WriteLine("\nWith Distinct True And With Id Field\n--------------------------------");
            results = crmSvc.OrganizationServiceProxy.RetrieveMultiple(new FetchExpression(
            @"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='true' count='1'>
	            <entity name='contact'>
                    <attribute name='contactid'/>
		            <attribute name='fullname'/>
	            </entity>
            </fetch>")).Entities.ToList();
            results.ForEach(x => Console.WriteLine("Id: {0}", x.Id));

            Console.WriteLine("\nWith Distinct False\n--------------------------------");
            results = crmSvc.OrganizationServiceProxy.RetrieveMultiple(new FetchExpression(string.Format(fetch, "distinct='false'"))).Entities.ToList();
            results.ForEach(x => Console.WriteLine("Id: {0}", x.Id));

            Console.WriteLine("\nWithout Distinct\n--------------------------------");
            results = crmSvc.OrganizationServiceProxy.RetrieveMultiple(new FetchExpression(string.Format(fetch, string.Empty))).Entities.ToList();
            results.ForEach(x => Console.WriteLine("Id: {0}", x.Id));

You get this output,

Output

As you can see, if you put distinct=true in your fetchxml, without retrieving the primary key, and iterate through the RetrieveMultiple result, the Id property will be Guid.Empty for all the entities in the EntityCollection.

Leverage Actions to bundle and minify form scripts

EDIT (26/05/15): I forgot to disable the plugin on RetrieveMultiple of webresource, described in the previous post. I also forgot to minify the script that runs on Form Experiment entity. Strangely however, this has reduced the performance. I tested this in CRMOnline, and so I am not sure if this is due to the load on the server at this time of the day. I have modified the performance screenshot, to reflect this.

I tried another approach to dynamically minify and load CRM form script. This time I am utilising Actions. I have tried different ideas with Actions in the past and love how extensible and powerful they are. Of all the approaches I have tried so far, I like this a lot. As usual, if you would rather read code, head to https://github.com/rajyraman/DynamicScriptBundling and fork the code.

This is the basic flow of this design:

  1. Form script just has code to invoke the action using current entityname as input parameter
  2. Action uses the Form Load Sequence entity to collate a list of scripts that have to be minified and concatenated. The minified and concatenated script is stored in the Action’s output parameter
  3. The calling form script, evals the returned minified and concatenated script

Before you get defensive aggressive about using evals, please read http://www.nczonline.net/blog/2013/06/25/eval-isnt-evil-just-misunderstood/. IMHO, it is perfectly alright to use eval in this scenario. Here are the steps:

Create the Action

The action doesn’t contain much config, other than the input parameter and output parameter. The maximum length of the string, if you set this from the UI is 255, however, if you use a plugin to do the same, there is no limit. We are going to use this behaviour, to set the minifiedscripts with the bundled/concatenated scripts.

Create the Plugin that is triggered by the Action

The Action’s input argument is passed on to the Plugin’s InputParameter and this is how the plugin knows, which entity it is dealing with. It then proceeds to concatenate and minify the scripts.

Plugin Code

using DouglasCrockford.JsMin;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Linq;
using System.Xml.Linq;
using Contract = System.Diagnostics.Contracts.Contract;

namespace RYR.Experiments.Plugins
{
    public class PostGetScriptsForEntity : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            Contract.Assert(serviceProvider != null, "serviceProvider is null");
            var pluginExecutionContext =
                (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            Contract.Assert(tracingService != null, "TracingService is null");

            try
            {
                var factory =
                    (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

                var organizationService = factory.CreateOrganizationService(pluginExecutionContext.UserId);
                var entityName = pluginExecutionContext.InputParameters["entityname"];

                var scriptLoadOrderFetchXml = string.Format(@"
                    <fetch count='1' >
                      <entity name='ryr_formloadsequence' >
                        <attribute name='ryr_name' />
                        <attribute name='ryr_scripts' />
                        <filter>
                          <condition attribute='ryr_name' operator='eq' value='{0}' />
                        </filter>
                      </entity>
                    </fetch>", entityName);
                var concatenatedScripts = new StringBuilder();
                var loadSequenceResults =
                    organizationService.RetrieveMultiple(new FetchExpression(scriptLoadOrderFetchXml))
                        .Entities;

                if (!loadSequenceResults.Any()) return;

                var scriptsToMerge = loadSequenceResults[0].GetAttributeValue<string>("ryr_scripts").Split(',');
                var webresourceFetchXml = string.Format(@"
                        <fetch>
                          <entity name='webresource' >
                            <attribute name='content' />
                            <attribute name='name' />
                            <filter>
                              <condition attribute='webresourcetype' operator='eq' value='3' />
                              <condition attribute='name' operator='in' >
                                {0}
                              </condition>
                            </filter>
                          </entity>
                        </fetch>", string.Join(string.Empty,
                         scriptsToMerge.Select(x => string.Format("<value>{0}</value>", x))));
                var toBeMergedWebResources = organizationService.RetrieveMultiple(
                    new FetchExpression(webresourceFetchXml)).Entities;

                if (!toBeMergedWebResources.Any()) return;

                foreach (var s in scriptsToMerge)
                {
                    var matchedWebresource = toBeMergedWebResources
                        .FirstOrDefault(x => x.GetAttributeValue<string>("name") == s);
                    if (matchedWebresource != null)
                    {
                        concatenatedScripts.AppendLine(Encoding.UTF8.GetString(Convert.FromBase64String(
                            matchedWebresource.GetAttributeValue<string>("content"))));
                    }
                    else
                    {
                        concatenatedScripts.AppendLine(
                            string.Format(
                                "Xrm.Page.ui.setFormNotification('Unable to load {0}', 'ERROR', '{1}');", s,
                                Guid.NewGuid()));
                    }
                }
                pluginExecutionContext.OutputParameters["minifiedscripts"] = new JsMinifier().Minify(concatenatedScripts.ToString());
            }
            catch (Exception e)
            {
                tracingService.Trace(e.StackTrace);
                throw;
            }
        }
    }
}

Use Sdk.Soap.js to generate Javascript code for Action

Download Sdk.Soap.js  and run the Sdk.SoapActionMessageGenerator.exe executable. Once you choose the organisation details and enter the credentials, the Javascript code for each individual action will be generated.

Modify the generated code to auto-populate entity name

The code generated by the Sdk.SoapActionMessageGenerator is going to be used in the form. We want to simply add this script to any entity, without having to change a single line of code. However, the Action accepts an input parameter called entity, that will obviously be different from entity to entity. Hence we have to make a slight modification to ensure that entityname is set automatically using Client API.

Change

(function() {
this.ryr_GetScriptsForEntityRequest = function (
entityname
)
{
///
/// 
///
///
/// [Add Description]
///
if (!(this instanceof Sdk.ryr_GetScriptsForEntityRequest)) {
return new Sdk.ryr_GetScriptsForEntityRequest(entityname);
}

to

function () {
this.ryr_GetScriptsForEntityRequest = function ()
{
var entityname = Xrm.Page.data.entity.getEntityName(); 
///
/// 
///
///
/// [Add Description]
///
if (!(this instanceof Sdk.ryr_GetScriptsForEntityRequest)) {
return new Sdk.ryr_GetScriptsForEntityRequest(entityname);
}

I also had problem with strict mode and so I removed it from both Sdk.Soap.js and Sdk.ryr_GetScriptsForEntity.vsdoc.js. Now we merge both these scripts into one and add that as a javascript webresource. This webresource is then added to the form.

Here is the script coming through in the Action response, before getting evaled.

Performance

 291 ms (minified and added to the form)
vs
 440 ms (dynamically minified using Action)
Hope this gives you an idea on how you can use Action to solve the script load sequence issue in CRM2015.

RetrieveMultiple performance on large datasets

EDIT (21/05/15): After DMing with @maustinjones, I have added stat for paged RetrieveMultiple as well. I have updated the post to include this.

EDIT (22/05/15): Updated post to reflect correct behaviour of no-lock when parallelising. Thanks @maustinjones. Please also refer to the follow up post -> https://dreamingincrm.com/2015/05/22/redux-retrievemultiple-on-large-datasets/ on why paging cookie is the recommended approach.

EDIT (18/07/16): Please watch this -> https://www.youtube.com/watch?v=-N20I5mo7RU video from the Dynamics CRM product group, which goes into much more detail about performance techniques to optimise RetrieveMultiple queries.

EDIT (25/01/17): Eric Hagen has written a series of post covering RetrieveMultiple performance and internals. Reading these posts is time well spent -> https://community.dynamics.com/crm/b/dynamicscrmsupportblog/archive/2017/01/11/retrievemultiple-performance

MSCRM limits the maximum result size to 5000 records, when you use the RetrieveMultiple request. In order to overcome this and retrieve all the records, you’ll have to use paging. PFEDynamics team have also released an open-source library called PFE Xrm Core Library that utilises Task Parallel Library.

There is also ExecuteMultipleRequest that you can use to send bunch of requests in one go, and process the responses. This just wanted to document by findings, about the performance of these options:

  1. Just using Parallel.ForEach
  2. ExecuteMultipleRequest
  3. PFE Xrm Core
  4. Paged RetrieveMultiple

Run 1 (Batch size 1000):
I have not included the paged RetrieveMultiple in this scenario, as it is too slow, and I am too impatient to wait for it to complete.

Run 2 (Batch size 5000):

Run 3 (Batch size 5000):

Observations:

  1. Reducing the page size causes a drop in performance. I got better performance with 5000 records in one page, than 1000 records
  2. Just using Parallel.ForEach is faster than PFE Xrm Core (some overhead in instantiating the service perhaps?)
  3. ExecuteMultipleRequest is significantly slower than Parallel.ForEach

Best Practice tip from PFE Dynamics:

If you read the source code for the ParallelServiceProxy class, the help text for the RetrieveMultiple method actually has this note:

IMPORTANT!! This approach should only be used if multiple queries for varying entity types are required or the result set can’t be expressed in a single query. In the latter case,
leverage NoLock=true where possible to reduce database contention.

So ideally, you should be using RetrieveMultiple method in ParallelProxy, to retrieve records from multiple entities. But, in my case I am retrieving from the same entity, hence I am using the no-lock option the fetchxml.

DMed with @maustinjones, and here is an important point to consider, before choosing a technique. 

Query pages shouldn’t be retrieved in parallel. only separate queries altogether

When you use TPL to submit a bunch of RetrieveMultiple requests in one go, there is a chance of encountering database lock issues, even though you use no-lock. So, it is OK to parallelise queries that fetch from different entities, but using PagingCookie is the recommended approach.

To be honest, I did not experience any lock issues during the test, but standard CRM load was not simulated during the test. I am not sure what would have happened, if users were viewing contacts/running reports on contacts, and I am running these tests at the same time.
Database contentention and locking is applicable only in scenarios, where no-lock is not feasible.

TPL just blitzes the other techniques in these tests, but I’ll have to test this further to see if lock contention issues arise, in much larger datasets. Do consider PFE Xrm Core, if you are looking to use RetrieveMultiple from multiple entities. It is written by the PFE Dynamics guys and is open-source.

Best Practice tips from msdn regarding TPL:

This article in msdn is a must read if you are thinking of using TPL -> https://msdn.microsoft.com/en-us/library/dd997392%28v=vs.110%29.aspx.

I had performance issues with TPL, as I had Console.WriteLine inside the lambda for ForEach, and it basically killed the performance. The article told me why, and so it is quite an useful read.

Note about the code:
I quickly wrote this just to test the performance, and not production quality in mind. If you read the code, you can see I am sending 20 requests at a time, inside an infinite loop to retrieve all the pages. I exit the loop when any one of the pages, returns no result. I haven’t tried ExecuteMultipleRequest inside the Parallel.ForEach. It would be interesting to see what the performance will be in that case.

Code: Here is the code, I tested with, for reference.

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Tooling.Connector;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace Playground
{
    class Program
    {
        static void Main(string[] args)
        {
            RetrieveAllPages(@"<fetch count='5000' no-lock='true' page='{0}' >
                                  <entity name='account' >
                                    <attribute name='name' />
                                    <order attribute='accountid' />
                                    <filter>
                                        <condition attribute='statecode' operator='eq' value='0' />
                                    </filter>
                                   </entity>
                                </fetch>");

            RetrieveAllPages(@"<fetch count='5000' no-lock='true' page='{0}' >
                                  <entity name='contact' >
                                    <attribute name='fullname' />
                                    <order attribute='contactid' />
                                    <filter>
                                      <condition attribute='statecode' operator='eq' value='0' />
                                    </filter>
                                  </entity>
                                </fetch>");
        }

        static void RetrieveAllPages(string fetchXml)
        {
            Console.WriteLine("Entity: {0}\n", XElement.Parse(fetchXml).Element("entity").Attribute("name").Value);
            var organisationSvcManager = new OrganizationServiceManager(new Uri("CRMURL"),
                "[username]", "[password]", "[domain]");
            var crmSvc = new CrmServiceClient(new NetworkCredential("[username]", "[password]", "[domain]"), Microsoft.Xrm.Tooling.Connector.AuthenticationType.AD, "CRMURL", "80", "[Org]");
            var isDone = false;
            int pageStart = 1, pageEnd = 20;
            if (crmSvc.IsReady)
            {
                var stopWatch = new Stopwatch();
                stopWatch.Start();
                var results = new List<Entity>();

                while (!isDone)
                {
                    IDictionary<string, QueryBase> queries = new Dictionary<string, QueryBase>();
                    //Console.WriteLine("Page {0} to {1}", pageStart, pageEnd);
                    for (int i = pageStart; i <= pageStart + 19; i++)
                    {
                        queries.Add(i.ToString(), new FetchExpression(string.Format(fetchXml, i)));

                    };
                    pageStart = pageEnd + 1;
                    pageEnd = pageStart + 19;
                    var threadNum = 1;
                    Parallel.ForEach(queries,
                        (query) =>
                        {
                            if (!isDone)
                            {
                                var pageResults = crmSvc.OrganizationServiceProxy.RetrieveMultiple(query.Value).Entities;
                                if (!pageResults.Any())
                                {
                                    isDone = true;
                                    return;
                                }
                                results.AddRange(pageResults);
                            }
                        });
                }

                Console.WriteLine(results.Count);
                stopWatch.Stop();
                Console.WriteLine("Parallel: Executed in {0} sec", stopWatch.ElapsedMilliseconds / 1000);
                Console.WriteLine(results.Count / (stopWatch.ElapsedMilliseconds / 1000) + " per sec\n");

                var resultCount = 0;
                pageStart = 1;
                pageEnd = 20;
                stopWatch.Restart();
                isDone = false;
                while (!isDone)
                {
                    //Console.WriteLine("Page {0} to {1}", pageStart, pageEnd);
                    var executeMultipleRequest = new ExecuteMultipleRequest
                    {
                        Requests = new OrganizationRequestCollection(),
                        Settings = new ExecuteMultipleSettings() { ContinueOnError = true, ReturnResponses = true }
                    };

                    for (var i = pageStart; i <= pageStart + 19; i++)
                    {
                        executeMultipleRequest.Requests.Add(new RetrieveMultipleRequest()
                        {
                            Query = new FetchExpression(string.Format(fetchXml, i))
                        });
                    };
                    var executeMultipleResponses = ((ExecuteMultipleResponse)crmSvc.OrganizationServiceProxy.Execute(executeMultipleRequest)).Responses.ToList();
                    executeMultipleResponses.ForEach(x =>
                    {
                        var executeMultipleResultEntities = ((RetrieveMultipleResponse) x.Response).EntityCollection.Entities;
                        if (!executeMultipleResultEntities.Any())
                        {
                            isDone = true;
                            return;
                        }
                        resultCount = resultCount + executeMultipleResultEntities.Count;
                    });
                    pageStart = pageEnd + 1;
                    pageEnd = pageStart + 19;
                }
                Console.WriteLine(resultCount);
                stopWatch.Stop();
                Console.WriteLine("ExecuteMultiple: Executed in {0} sec", stopWatch.ElapsedMilliseconds / 1000);
                Console.WriteLine(results.Count / (stopWatch.ElapsedMilliseconds / 1000) + " per sec\n");

                IDictionary<string, QueryBase> entityQuery = new Dictionary<string, QueryBase>();
                entityQuery.Add("result", new FetchExpression(fetchXml));
                stopWatch.Restart();
                var queryResult = organisationSvcManager.ParallelProxy.RetrieveMultiple(entityQuery, true,
                    (pair, exception) => Console.WriteLine("{0} throwed {1}", pair.Key, exception.Message));
                stopWatch.Stop();
                Console.WriteLine(queryResult.Values.First().Entities.Count);
                Console.WriteLine("PFE.Xrm: Executed in {0} sec", stopWatch.ElapsedMilliseconds / 1000);
                Console.WriteLine(queryResult.Values.First().Entities.Count / (stopWatch.ElapsedMilliseconds / 1000) + " per sec\n");

                resultCount = 0;
                var pagedResults = new EntityCollection();
                var fetchToQuery = new FetchXmlToQueryExpressionRequest {FetchXml = string.Format(fetchXml, 1)};
                var retrieveQuery = ((FetchXmlToQueryExpressionResponse)crmSvc.OrganizationServiceProxy.Execute(fetchToQuery)).Query;
                retrieveQuery.PageInfo = new PagingInfo {PageNumber = 1};
                stopWatch.Restart();
                do
                {
                    pagedResults = crmSvc.OrganizationServiceProxy.RetrieveMultiple(retrieveQuery);
                    resultCount += pagedResults.Entities.Count;
                    retrieveQuery.PageInfo.PageNumber++;
                    retrieveQuery.PageInfo.PagingCookie = pagedResults.PagingCookie;
                } while (pagedResults.MoreRecords);
                Console.WriteLine(resultCount);
                stopWatch.Stop();
                Console.WriteLine("Paged RetrieveMultiple: Executed in {0} sec", stopWatch.ElapsedMilliseconds / 1000);
                Console.WriteLine(resultCount / (stopWatch.ElapsedMilliseconds / 1000) + " per sec\n");
            }
        }
    }
}

Plugin Registration Tool and Plugin Profile

Having spent most of today trying out a failed plugin experiment, I discovered a feature of Plugin Registration tool that I never used before. It it not very clear from the SDK documentation, what keys are there in the InputParameters and OutputParameters and often I refer the appropriate request/response class, to find out what key I should be using on the InputParameter e.g. for UpdateRequest use Target key on the InputParameter for a pre-operation plugin.There seems to be a easier way to capture the plugin state right after the plugin fires. This can be done using the Plugin Registration tool. This is the plugin command bar:

Replay Plug-in Execution button is used when you want to debug the plugin in Visual Studio. This process is well documented in multiple blogs and SDK documentation. View Plug-in profile functionality is what we want to use during the initial plugin development stage. After registering a plugin, right click on the desired step and choose Start Profiling.

Persist the profile to entity, instead of throwing an exception.

Once this is done, perform the action that triggers the plugin. This creates a profile record, that we can use to dump the plugin state. After the plugin has finished executing, click the view plugin profile button in the command bar. It brings up this window.

Next, click the downarrow button and choose the appropriate trace record.

After clicking the View button, you can see the entire plugin state. The below is the xml I got for a plugin firing on Retrieve message on SavedQuery.

<Profile>
  <Configuration i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" />
  <ConstructorDurationInMilliseconds>2</ConstructorDurationInMilliseconds>
  <ConstructorException i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" />
  <ConstructorStartTime>2015-05-06T01:46:45.5675566Z</ConstructorStartTime>
  <Context>
    <z:anyType i:type="PluginExecutionContext" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
      <BusinessUnitId>1f06daae-de79-e411-80bd-00155dc6799a</BusinessUnitId>
      <CorrelationId>09993a86-41c8-40b7-bc4c-2717057b5408</CorrelationId>
      <Depth>1</Depth>
      <InitiatingUserId>af20daae-de79-e411-80bd-00155dc6799a</InitiatingUserId>
      <InputParameters xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic">
        <a:KeyValuePairOfstringanyType>
          <b:key>Target</b:key>
          <b:value i:type="a:EntityReference">
            <a:Id>00000000-0000-0000-00aa-000010001001</a:Id>
            <a:LogicalName>savedquery</a:LogicalName>
            <a:Name i:nil="true" />
          </b:value>
        </a:KeyValuePairOfstringanyType>
        <a:KeyValuePairOfstringanyType>
          <b:key>ColumnSet</b:key>
          <b:value i:type="a:ColumnSet">
            <a:AllColumns>false</a:AllColumns>
            <a:Columns xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
              <c:string>fetchxml</c:string>
              <c:string>name</c:string>
              <c:string>description</c:string>
              <c:string>returnedtypecode</c:string>
              <c:string>layoutxml</c:string>
              <c:string>savedqueryid</c:string>
              <c:string>name</c:string>
              <c:string>statecode</c:string>
              <c:string>statuscode</c:string>
              <c:string>ismanaged</c:string>
              <c:string>iscustomizable</c:string>
            </a:Columns>
          </b:value>
        </a:KeyValuePairOfstringanyType>
        <a:KeyValuePairOfstringanyType>
          <b:key>ReturnNotifications</b:key>
          <b:value i:type="c:boolean" xmlns:c="http://www.w3.org/2001/XMLSchema">true</b:value>
        </a:KeyValuePairOfstringanyType>
        <a:KeyValuePairOfstringanyType>
          <b:key>RelatedEntitiesQuery</b:key>
          <b:value i:nil="true" />
        </a:KeyValuePairOfstringanyType>
      </InputParameters>
      <IsExecutingOffline>false</IsExecutingOffline>
      <IsInTransaction>false</IsInTransaction>
      <IsOfflinePlayback>false</IsOfflinePlayback>
      <IsolationMode>2</IsolationMode>
      <MessageName>Retrieve</MessageName>
      <Mode>0</Mode>
      <OperationCreatedOn>2015-05-06T01:46:45.5206856Z</OperationCreatedOn>
      <OperationId>00000000-0000-0000-0000-000000000000</OperationId>
      <OrganizationId>a240c2fd-16a0-4ab5-9ecb-a43a6afdcc8c</OrganizationId>
      <OrganizationName>Contoso</OrganizationName>
      <OutputParameters xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic">
        <a:KeyValuePairOfstringanyType>
          <b:key>BusinessEntity</b:key>
          <b:value i:type="a:Entity">
            <a:Attributes>
              <a:KeyValuePairOfstringanyType>
                <b:key>savedqueryid</b:key>
                <b:value i:type="z:guid">00000000-0000-0000-00aa-000010001001</b:value>
              </a:KeyValuePairOfstringanyType>
              <a:KeyValuePairOfstringanyType>
                <b:key>fetchxml</b:key>
                <b:value i:type="c:string" xmlns:c="http://www.w3.org/2001/XMLSchema"><fetch version="1.0" output-format="xml-platform" mapping="logical"><entity name="account"><attribute name="name" /><attribute name="address1_city" /><order attribute="name" descending="false" /><filter type="and"><condition attribute="ownerid" operator="eq-userid" /><condition attribute="statecode" operator="eq" value="0" /></filter><attribute name="primarycontactid" /><attribute name="telephone1" /><attribute name="accountid" /><link-entity alias="accountprimarycontactidcontactcontactid" name="contact" from="contactid" to="primarycontactid" link-type="outer" visible="false"><attribute name="emailaddress1" /></link-entity></entity></fetch></b:value>
              </a:KeyValuePairOfstringanyType>
              <a:KeyValuePairOfstringanyType>
                <b:key>returnedtypecode</b:key>
                <b:value i:type="c:string" xmlns:c="http://www.w3.org/2001/XMLSchema">account</b:value>
              </a:KeyValuePairOfstringanyType>
              <a:KeyValuePairOfstringanyType>
                <b:key>name</b:key>
                <b:value i:type="c:string" xmlns:c="http://www.w3.org/2001/XMLSchema">My Active Accounts</b:value>
              </a:KeyValuePairOfstringanyType>
              <a:KeyValuePairOfstringanyType>
                <b:key>statuscode</b:key>
                <b:value i:type="a:OptionSetValue">
                  <a:Value>1</a:Value>
                </b:value>
              </a:KeyValuePairOfstringanyType>
              <a:KeyValuePairOfstringanyType>
                <b:key>iscustomizable</b:key>
                <b:value i:type="a:BooleanManagedProperty">
                  <a:CanBeChanged>true</a:CanBeChanged>
                  <a:ManagedPropertyLogicalName>iscustomizableanddeletable</a:ManagedPropertyLogicalName>
                  <a:Value>true</a:Value>
                </b:value>
              </a:KeyValuePairOfstringanyType>
              <a:KeyValuePairOfstringanyType>
                <b:key>layoutxml</b:key>
                <b:value i:type="c:string" xmlns:c="http://www.w3.org/2001/XMLSchema"><grid name="resultset" object="1" jump="name" select="1" icon="1" preview="1"><row name="result" id="accountid"><cell name="name" width="300" /><cell name="telephone1" width="100" /><cell name="address1_city" width="100" /><cell name="primarycontactid" width="150" /><cell name="accountprimarycontactidcontactcontactid.emailaddress1" width="150" disableSorting="1" /></row></grid></b:value>
              </a:KeyValuePairOfstringanyType>
              <a:KeyValuePairOfstringanyType>
                <b:key>ismanaged</b:key>
                <b:value i:type="c:boolean" xmlns:c="http://www.w3.org/2001/XMLSchema">true</b:value>
              </a:KeyValuePairOfstringanyType>
              <a:KeyValuePairOfstringanyType>
                <b:key>statecode</b:key>
                <b:value i:type="a:OptionSetValue">
                  <a:Value>0</a:Value>
                </b:value>
              </a:KeyValuePairOfstringanyType>
            </a:Attributes>
            <a:EntityState i:nil="true" />
            <a:FormattedValues>
              <a:KeyValuePairOfstringstring>
                <b:key>returnedtypecode</b:key>
                <b:value>Account</b:value>
              </a:KeyValuePairOfstringstring>
              <a:KeyValuePairOfstringstring>
                <b:key>statuscode</b:key>
                <b:value>Active</b:value>
              </a:KeyValuePairOfstringstring>
              <a:KeyValuePairOfstringstring>
                <b:key>iscustomizable</b:key>
                <b:value>True</b:value>
              </a:KeyValuePairOfstringstring>
              <a:KeyValuePairOfstringstring>
                <b:key>ismanaged</b:key>
                <b:value>Managed</b:value>
              </a:KeyValuePairOfstringstring>
              <a:KeyValuePairOfstringstring>
                <b:key>statecode</b:key>
                <b:value>Active</b:value>
              </a:KeyValuePairOfstringstring>
            </a:FormattedValues>
            <a:Id>00000000-0000-0000-00aa-000010001001</a:Id>
            <a:LogicalName>savedquery</a:LogicalName>
            <a:RelatedEntities />
          </b:value>
        </a:KeyValuePairOfstringanyType>
      </OutputParameters>
      <OwningExtension xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts">
        <a:Id>517294bb-91f3-e411-80d0-00155dc6799a</a:Id>
        <a:LogicalName>sdkmessageprocessingstep</a:LogicalName>
        <a:Name>RYR.VirtualViews.SavedQueryOnRetrieveMultiplePlugin: Retrieve of savedquery (Profiler)</a:Name>
      </OwningExtension>
      <PostEntityImages xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
      <PreEntityImages xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
      <PrimaryEntityId>00000000-0000-0000-00aa-000010001001</PrimaryEntityId>
      <PrimaryEntityName>savedquery</PrimaryEntityName>
      <RequestId i:nil="true" />
      <SecondaryEntityName>none</SecondaryEntityName>
      <SharedVariables xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
      <UserId>af20daae-de79-e411-80bd-00155dc6799a</UserId>
      <ParentContext i:nil="true" />
      <Stage>40</Stage>
    </z:anyType>
  </Context>
  <ExecutionDurationInMilliseconds>8</ExecutionDurationInMilliseconds>
  <ExecutionException i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" />
  <ExecutionStartTime>2015-05-06T01:46:45.5675566Z</ExecutionStartTime>
  <HasServiceEndpointNotificationService>true</HasServiceEndpointNotificationService>
  <IsContextReplay>false</IsContextReplay>
  <IsolationMode>2</IsolationMode>
  <OperationType>Plugin</OperationType>
  <ProfileVersion>1.1</ProfileVersion>
  <ReplayEvents xmlns:a="http://schemas.datacontract.org/2004/07/PluginProfiler.Plugins" />
  <SecureConfiguration i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" />
  <TypeName>RYR.VirtualViews.SavedQueryOnRetrieveMultiplePlugin</TypeName>
  <WorkflowInputParameters xmlns:a="http://schemas.datacontract.org/2004/07/PluginProfiler.Plugins" />
  <WorkflowOutputParameters xmlns:a="http://schemas.datacontract.org/2004/07/PluginProfiler.Plugins" />
  <WorkflowStepId i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" />
</Profile>

As you can see, this file is quite useful in trying to understand the plugin execution context and can be beneficial in the initial development stage. This feature has been in PluginRegistration tool since CRM2011, and I felt like an idiot for having just discovered it, but happy that I finally discovered this. I will definitely be using this a lot in the coming days.

GetAttributeValue and null DateTime

Previously, I used to access the value of a property like entity[“attributename”] and cast the result into the appropriate type. The preceding line to this, would always be a check to see if the property exists, as it can cause a KeyNotFoundException, without this check. GetAttributeValue is how I access attribute values these days.

These are some influential posts that made me change my behaviour.
David Berry -> http://crmentropy.blogspot.com.au/2013/08/entitygetattributevalue-explained.html
Guido Preite -> http://www.crmanswers.net/2014/09/getattributevalue-activityparty.html

While this does prevent KeyNotFoundException, it is important to understand the behaviour of GetAttributeValue, w.r.t DateTime. When GetAttributeValue is invoked to retrieve a DateTime attribute, and the value of the attribute is null, it returns a DateTime.MinValue, which is 01/01/0001.

In a scenario where a retrieved value is used to update another record, you’ll have to check if this is DateTime.MinValue before updating, or it will cause an exception like the one below.

The exception thrown is “DateTime is less than minumum[sic] value supported by CrmDateTime. Actual value: 01/01/0001 11:00:00, Minimum value supported: 01/01/1900 00:00:00″.

To prevent this exception, I check if the retrieved DateTime value == DateTime.MinValue, and if so, choose not to update the target property, or set it as null, depending on the requirement. It is also a realisation for me, that CrmDateTime still lives on, somewhere in the Sdk assemblies.

EDIT (29/01/15): Following David’s tip from the comment below, the better approach is to use nullable types with GetAttributeValue, so I should be using DateTime? instead of DateTime.

Quick Tip: Don’t use underscore in Action argument name

There seems to be a bug in the process editor, when you use it to define an action that contains an argument with an underscore in the name. Once you save this action, which meets this criteria, you will not be able to open the action definition again through the process editor. You just get a generic error dialog when you try to open the action.

My action definition itself is minimal. It doesn’t contain anything other than the argument (screenshot after following recovery steps).

The underlying error that is found in the url is:

Error code: 0x80040216
Error description: Invalid variable name format

In order to recover from the error follow these steps

1.) Create a new solution and add the action to this solution
2.) Export the solution as an unmanaged solution
3.) Unzip the solution to a location
4.) In the workflow folder, you will find a xaml file. Open this using any text editor
5.) Find and replace the parameter name which has the underscore, to be without underscore
6.) Rezip and import

The action can be opened again after following these steps. I tested this issue and can confirm that it  happens in CRM 2013 6.1.1 and CRM Online.

Business Rules by Form Type

Xrm.Page.ui.getFormType() is used in form script to find out what type of form is currently loaded. Sometimes, we want to apply a certain logic, depending on whether it is a create form or update form. e.g I want to disable some fields, if it is an update form.

If we are using Business Rules, it is not very obvious (at least to me) on how this can be achieved. The answer is quite simple: just check the value of any of these system fields (created, createdon, modifiedby, modifiedon).

Here is a business rule that will trigger only for update form.

Here is the rule for create form.

Here is the result after the rule has run on an existing record

The important thing to remember is: The system field you are checking (in this case createdon), has to be on the form. Otherwise the rule will not fire.

Credits to @BernadoNH for this info.

Quicktip: Install .net 4.5.2 before developing for CRM2015

Before you start developing console application, workflow or plugin for CRM2015 the first step is to install .net 4.5.2 as this is required by CRM2015. I was working on a simple console application using CRM2015 assemblies and I encountered a weird error. I ran “Install-Package Microsoft.CrmSdk.XrmTooling.CoreAssembly” from the PM console without any issue. But when you try to build the application I got these compilation errors.

The root cause of these errors is because the project is not targetting .net 4.5.2. Download .net 4.5.2 from http://www.microsoft.com/en-us/download/details.aspx?id=42637 and update the project target framework to .net 4.5.2. This time the build process succeeds. I was quite surprised that nuget did not prevent me from using the package in the project, even though I didn’t have a the prerequisite framework version.