Bug: Entity primary field & realtime workflows

When you create a new entity, one of the fields that is generally left unchanged is the primary field for the entity. It is usually “[publisherprefix]_name”.

image

This field can be either required or left optional. This value of this field, is what shows up in the entity lookups. The value of this field is usually set automatically using JavaScript, Workflow or Plugin. If the value for this field is left null, it manifests a bug in the realtime workflow.

What is the bug?

If the value of the primary field is null on a parent entity, any child entity can’t use “if condition” on the parent relationship field. It would result in a “KeyNotFound” exception.

Replication steps

  1. Create an entity that will be the parent entity
  2. Create an entity that will be the child. Create a relationship to the parent entity image
  3. Make the primary field on the parent entity as optional image
  4. Create a new realtime workflow on the child entity, with a condition statement that refers the parent lookup field in the child entity image
  5. Create a parent record without the name field setimage
  6. Create a child record that has the parent lookup set the record created in the previous step. The parent lookup will be displayed as “no name” as the primary field for the parent entity is not set.image
  7. Execute the realtime workflow against the child record in the previous step. A “The given key was not present in the dictionary” exception will be displayedimage

The issue is happening both in CRM 2015 and CRMOnline on version 8.1.0.362. This issue seems to be only affecting realtime workflows and actions, and not background workflows.

EDIT (24/06/2016): Issue logged on Connect -> https://connect.microsoft.com/site687/feedback/details/2851391/realtime-workflow-action-crash-display-name-if-condition-keynotfoundexception

Advertisements

Gotcha: Optional Action Parameters

Yesterday, when I was looking into some failed tests, I discovered a quirk of action. If you invoke an action and don’t pass in the arguments that are optional, it doesn’t mean this is going to come up as null in the execution context.

Below is the action I used to replicate the behaviour.

Optional

When I execute the action and don’t set any arguments, here is what comes through in the execution context.

Argument Type Value
 Boolean  false
 DateTime  0001-01-01T00:00:00
 Decimal  0
 Entity  null
 EntityReference  null
 Float  0
 Int  0
 Money  null
 Picklist  null
 String  null
 EntityCollection  null

The interesting things to note are:

  1. Action arguments, don’t behave the same as AttributeCollection. When you retrieve an entity using the SDK, get the attribute just using the key (not GetAttributeValue), the key won’t exist in the collection, if the attribute value is null. This is not the same behaviour with the action arguments. The key will always be there in the context, even if was not set during the action invocation.
  2. The default value of action arguments = default value of the primitive type

This essentially means that if you are going to do some processing based on the action argument that is a primitive type (Boolean, DateTime, Decimal, Float, Int, String), don’t make it optional. For e.g. if you make a boolean argument optional, how would you differentiate between an action invocation with the argument set to false, and another one which did not set the argument at all?

tl;dr; For action arguments (both input and output) of type Boolean, DateTime, Decimal, Float, Int and String avoid optional.

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.

Using actions to avoid intermediate entity

When actions were first introduced into Dynamics CRM, I did not quite understand its potential and appreciate how useful they are. But after using actions in quite a few projects, I am true believer of actions. One scenario, where I found actions quite useful is, when you have a intermediate entity, just for triggering a workflow, actions can be used to eliminate the unnecessary entity.

Here a (somewhat) simplified version of an design I worked on recently.

The SSIS Job looks into Invoice, Contact and Communication Configuration entities and creates a Communication entity record. The Communication entity stores information from both Contact and Invoice entity. It also uses Communication Configuration entity to calculate a trigger date for sending the communication.

In this design, the Communication entity exists solely for the Send Communication workflow. The entity itself doesn’t serve any useful purpose beyond that. This entity has collated some information from Invoice, Contact and Communication Configuration entities and this is used by the Send Communication workflow.

In this scenario the Communication entity can be replaced by an Action. Any attributes that are defined on the Communication entity, simply translate into input arguments on the action. The workflow that is triggered by create of Communication entity can simply be triggered from the Action. Once the Communication entity has been swapped over for an action, the SSIS task calls the action using a script task, instead of creating a Communication record.

The only disadvantage I see of using this approach is that there is no historial information when you use action i.e. I can’t simply use an Advanced Find to see when an action executed and with what parameters (unless you log it somewhere). I would get this capability if I used an entity, as I will store this information in the entity.

Bug: Limitation of Action EntityReference Input Argument

Actions are great for lot of things, but there are few bugs/limitations that I experienced and found particularly frustrating. One of such bugs is that you cannot use the attributes of an entityreference input argument on an action step. This is not the same with custom workflow step with an entityreference output. You would be able to use any attribute from the entityreference output off the custom workflow step.Here is the action definition

The entityreference action argument is available in the dropdown and all the properties appear as if they can be used.

Here is the create email step with values from workflow and action argument being used.

I also have a custom workflow step that accepts an entityreference input and just sets the entityreference output.

The custom workflow step input is set the action input argument.

This code for the custom workflow step is quite simple.

using System.Activities;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Workflow;

namespace TestWorkflows
{
    public sealed class ActionParameterToContactEntityReference : CodeActivity
    {
        [Input("Contact")]
        [ReferenceTarget("contact")]
        [RequiredArgument]
        public InArgument ContactInput { get; set; }

        [Output("Contact")]
        [ReferenceTarget("contact")]
        public OutArgument ContactOutput { get; set; }

        protected override void Execute(CodeActivityContext executionContext)
        {
            ContactOutput.Set(executionContext, ContactInput.Get(executionContext));
        }
    }
}

This is the email activity after the action is created.

As you can see, the Email and Firstname properties from the action’s input entity reference comeup as blank, but there is no such issue with the ones retrieved from the custom workflow step.

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.

Executing Quick Find from Console Application : Redux

If you have been following my blog, you might remember this (https://dreamingincrm.com/2014/05/07/executing-quickfind-using-crm-sdk/) post about executing a quick find query from the console. It was using an undocumented message, and hence it is unsupported.

I had a crack at this problem one more time, this time using Actions.In order to return the quick find results, the custom action need to have a EntityCollection output parameter. I posted this (https://community.dynamics.com/crm/f/117/t/128534.aspx) question in CRM forums sometime back and didn’t get any response.

This is not production ready code and just demonstrates how this can be done. If you would rather read the code, instead of this post please find the download link in the very bottom.

Requirements:
For the search – should be able to specify:
1.) Entity name
2.) Search term
3.) Page number
4.) Number of records to be returned

Step 1: Create a custom action

 
The body of the action is empty and doesn’t contain any logic. The actual quickfind will be performed by a plugin registered post-operation of this action.
Step 2: Create the plugin
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 QuickFindAction.Plugins
{
    public class QuickFindPlugin : IPlugin
    {
        internal IOrganizationService OrganizationService
        {
            get;

            private set;
        }

        internal IPluginExecutionContext PluginExecutionContext
        {
            get;

            private set;
        }

        internal ITracingService TracingService
        {
            get;

            private set;
        }

        public void Execute(IServiceProvider serviceProvider)
        {
            Contract.Assert(serviceProvider != null, "serviceProvider is null");
            PluginExecutionContext =
                (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            TracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            Contract.Assert(TracingService != null, "TracingService is null");

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

                OrganizationService = factory.CreateOrganizationService(this.PluginExecutionContext.UserId);

                Contract.Assert(PluginExecutionContext.InputParameters.Contains("SearchTextInput"), "No SearchTextInput property");
                Contract.Assert(
                    !string.IsNullOrEmpty(PluginExecutionContext.InputParameters["SearchTextInput"].ToString()), "SearchTextInput is null or empty");
                Contract.Assert(PluginExecutionContext.InputParameters.Contains("EntityNameInput"), "No EntityNameInput property");

                string searchText = PluginExecutionContext.InputParameters["SearchTextInput"].ToString(),
                       searchEntity = PluginExecutionContext.InputParameters["EntityNameInput"].ToString();

                var savedViewQuery = string.Format(
                    @"<fetch version=""1.0"" output-format=""xml-platform"" mapping=""logical"" distinct=""false"">
                      <entity name=""savedquery"">
                        <attribute name=""fetchxml"" />
                        <filter type=""and"">
                          <condition attribute=""statecode"" operator=""eq"" value=""0"" />
                          <condition attribute=""isquickfindquery"" operator=""eq"" value=""1"" />
                          <condition attribute=""isdefault"" operator=""eq"" value=""1"" />
                          <condition attribute=""name"" operator=""like"" value=""%{0}%"" />
                        </filter>
                      </entity>
                    </fetch>", searchEntity);

                var quickFindFetchXml =
                    OrganizationService.RetrieveMultiple(new FetchExpression(savedViewQuery)).Entities[0].GetAttributeValue<string>("fetchxml");
                    TracingService.Trace("FetchXml read from SavedView");
                    var entityFetchXml = XElement.Parse(string.Format(quickFindFetchXml, string.Format("%{0}%", searchText)));

                if (PluginExecutionContext.InputParameters["Page"] != null)
                {
                    entityFetchXml.SetAttributeValue("page", PluginExecutionContext.InputParameters["Page"]);
                }
                if (PluginExecutionContext.InputParameters["Count"] != null)
                {
                    entityFetchXml.SetAttributeValue("count", PluginExecutionContext.InputParameters["Count"]);
                }

                entityFetchXml.Elements().Elements("filter").Elements().ToList().ForEach(x => {
                                                                                                  if (
                                                                                                      x.Attribute(
                                                                                                          "attribute")
                                                                                                          .Value
                                                                                                          .EndsWith("id"))
                                                                                                  {
                                                                                                      x.SetAttributeValue("attribute",x.Attribute("attribute").Value+"name");
                                                                                                  } });
                PluginExecutionContext.OutputParameters["FetchXml"] = entityFetchXml.ToString();

                var results = OrganizationService.RetrieveMultiple(new FetchExpression(entityFetchXml.ToString()));
                PluginExecutionContext.OutputParameters["SearchResultsOutput"] = new EntityCollection(results.Entities.ToList());
            }
            catch (Exception e)
            {
                TracingService.Trace(e.StackTrace);
                PluginExecutionContext.OutputParameters["Exception"] = e.StackTrace;
                throw;
            }
        }
    }
}

Step 3: Register the plugin

Step 4: Create the Console Application

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Tooling.Connector;

namespace ActionsTester
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                var executeQuickFindRequest = new OrganizationRequest("ryr_Search");
                executeQuickFindRequest["SearchTextInput"] = "sus";
                executeQuickFindRequest["EntityNameInput"] = "contact";
                executeQuickFindRequest["Page"] = 1;
                //executeQuickFindRequest["Count"] = 1;

                var crmSvc =
                    new CrmServiceClient(new NetworkCredential("administrator", "p@ssw0rd1", "CRM"),
                                         AuthenticationType.AD, "crm1", "80", "Contoso");
                if (crmSvc.IsReady)
                {
                    crmSvc.OrganizationServiceProxy.Execute(executeQuickFindRequest);
                    OrganizationResponse response = crmSvc.OrganizationServiceProxy.Execute(executeQuickFindRequest);
                    if (response.Results.Contains("Exception") &amp;&amp; response.Results["Exception"] != null)
                    {
                        Console.WriteLine(response.Results["Exception"]);
                        Console.WriteLine(response.Results["FetchXml"]);
                        return;
                    }
                    if (response.Results["SearchResultsOutput"] != null)
                    {
                        Console.WriteLine(response.Results["FetchXml"]);
                        var results = (EntityCollection) response.Results["SearchResultsOutput"];
                        foreach (var record in results.Entities)
                        {
                            record.Attributes.ToList().ForEach(x=> Console.WriteLine("{0}={1}",x.Key,Unwrap(x.Value)));
                            Console.WriteLine();
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.StackTrace);
            }
        }

        private static object Unwrap(object attributeValue)
        {
            var unwrappedValue = attributeValue;
            if (attributeValue is EntityReference)
            {
                unwrappedValue = ((EntityReference) attributeValue).Name;
            }
            else
                if (attributeValue is OptionSetValue)
                {
                    unwrappedValue = ((OptionSetValue)attributeValue).Value;
                }
            else
                if (attributeValue is Money)
                {
                    unwrappedValue = ((Money)attributeValue).Value;
                }
            return unwrappedValue;
        }
    }
}

Output:
The search term is “sus” and the entity is contact

Limitations:
1.) Search term is field type agnostic (exception of a lame entity reference mapping). So if a Optionset is in the quickfind query, the search term won’t work properly
2.) Assumption is made that the quick find view name has the entity name. So if entity name is contact and the quick find view doesn’t have the word contact this won’t work properly.
3.) Assumption is made that schema name for lookups end with id.

Problems Faced:
I was initially using strongly typed entities with a generated service context in the plugin, but had some serialisation exceptions along the way and decided to switch to query approach to get the application working. The exception was

>System.Runtime.Serialization.SerializationException: Microsoft Dynamics CRM has experienced an error. Reference number for administrators or support: #59D91113: 
System.Runtime.Serialization.SerializationException: Element 'http://schemas.microsoft.com/xrm/2011/Contracts:Entity' contains data from a type that maps to the name 'Contoso.EarlyBound.Generated:Contact'. 
The deserializer has no knowledge of any type that maps to this name. Consider changing the implementation of the ResolveName method on your DataContractResolver to return a non-null value for name 'Contact' 
and namespace 'Contoso.EarlyBound.Generated'.
>   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator reader, String name, String ns, Type declaredType, DataContract&amp; dataContract)
>   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator xmlReader, Int32 id, RuntimeTypeHandle declaredTypeHandle, String name, String ns)
>   at ReadArrayOfEntityFromXml(XmlReaderDelegator , XmlObjectSerializerReadContext , XmlDictionaryString , XmlDictionaryString , CollectionDataContract )
>   at System.Runtime.Serialization.CollectionDataContract.ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context)
>   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator reader, String name, String ns, Type declaredType, DataContract&amp; dataContract)
>   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator xmlReader, Int32 id, RuntimeTypeHandle declaredTypeHandle, String name, String ns)
>   at ReadEntityCollectionFromXml(XmlReaderDelegator , XmlObjectSerializerReadContext , XmlDictionaryString[] , XmlDictionaryString[] )
>   at System.Runtime.Serialization.ClassDataContract.ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context)
>   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator reader, String name, String ns, Type declaredType, DataContract&amp; dataContract)
>   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator xmlReader, Int32 id, RuntimeTypeHandle declaredTypeHandle, String name, String ns)
>   at ReadKeyValuePairOfstringanyTypeFromXml(XmlReaderDelegator , XmlObjectSerializerReadContext , XmlDictionaryString[] , XmlDictionaryString[] )
>   at System.Runtime.Serialization.ClassDataContract.ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context)
>   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator reader, String name, String ns, Type declaredType, DataContract&amp; dataContract)
>   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator xmlReader, Int32 id, RuntimeTypeHandle declaredTypeHandle, String name, String ns)
>   at ReadParameterCollectionFromXml(XmlReaderDelegator , XmlObjectSerializerReadContext , XmlDictionaryString , XmlDictionaryString , CollectionDataContract )
>   at System.Runtime.Serialization.CollectionDataContract.ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context)
>   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator reader, String name, String ns, Type declaredType, DataContract&amp; dataContract)
>   at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator xmlReader, Type declaredType, DataContract dataContract, String name, String ns)
>   at System.Runtime.Serialization.DataContractSerializer.InternalReadObject(XmlReaderDelegator xmlReader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
>   at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName, DataContractResolver dataContractResolver)
>   at System.Runtime.Serialization.XmlObjectSerializer.ReadObject(XmlDictionaryReader reader)
>   at Microsoft.Crm.Sandbox.SandboxUtility.DeserializeDataContract[T](Byte[] serializedDataContract, Assembly proxyTypesAssembly)
>   at Microsoft.Crm.Sandbox.SandboxExecutionContext.Merge(IExecutionContext originalContext)
>   at Microsoft.Crm.Sandbox.SandboxCodeUnit.Execute(IExecutionContext context)

Observations:
1.) I was surprised to see address1_composite on the result set even though I did not mention it in the search find query. Running the exact same query in XrmToolBox FetchXml Tester doesn’t return address1_composite field.
2.) count=0 on a fetchxml somehow works and returns records if matches are found.

Improvements that can be made:
1.) Parse the attributes in quickfind fetchxml and retrieve the types of these attributes, so that the search query can be correctly mapped.
2.) Use PFE Core Library on the retrieve part
3.) Add additional types to the unwrapping code in console application (currently unwraps only entityreference, optionsetvalue and money).

Conclusion:
Actions are AWESOME. I can write a logic once and can call this from console application, workflow or javascript. Previously you would have to encapsulate this logic on a webservice, to get this kind of extensibility.

Code: http://1drv.ms/1w5GJ2b