Tuesday, June 2, 2009

Execute Query - Activity in MS CRM Workflow - yes, it's possible!

One thing lacking from the standard set of workflow activities is a Query Activity. Especially if I have to work with a heavily customized data model, retrieving values can often be quite a pain. Often I need to include or work on such values in a business process, and I prefer to use workflow for just that. Another limitation is that there's just a subset of the relations that's available for a given record when that record is used in a workflow.

Consider the following example: A CRM user creates a new case on a customer account. The client has requested that whenever a case is created, a search for certain information should be conducted. That could be something completely unrelated to the case, say a custom entity that holds information about projects the client has done for this customer.

How do you facilitate that? The case is related to the customer account, and the customer account is related to the custom project entity, but there's no way that custom entity is accessible to the workflow if the workflow is triggered by the creation of the case. You'll only get the customer account information, nothing deeper than that.

So you have to write some sort of plugin business logic that executes a query and updates some information. Developing for MS CRM means writing a lot of boring queries, so you're probably used to this. But it'll mean several hours spent testing, debugging and rewriting that plugin - hours you could spend on something else.

How? Just write a simple Query Activity to use with your workflow. It's quite simple, and wont solve every need, but I guess it'll take you quite far in eliminating the need to write all those boring little queries.

The consept is to use a workflow with four input properties. These four properties will take information about your query, execute it, and return an output string property with the result. The main limitations are that you can only retrieve a single value for an attribute, and there's no support for a linked entity (Though it would be quite easy to implement it). Furthermore, you can't specify the output type (But since it's string you can reuse it in other queries).

The custom workflow activity utilizes the DynamicEntity-y class so you can use this on any entity you want. It will also do a cast based on the property type of the result, and return the text for Picklist and Status, and a Guid-string for Key and Owner. The rest of the types vil just convert the value to a string.

In order to make this custom workflow activity, start up Visual Studio, create a new class library, reference the dll's and the System.Workflow - namespaces, and paste in the following code:

[CrmWorkflowActivity("Query Activity")]
public class WorkflowQuery: Activity
{
public static DependencyProperty entitynameProperty = DependencyProperty.Register("entityname", typeof(string), typeof(WorkflowQuery));
[CrmInput("Entity Name")]
[CrmDefault("account")]
public string entityname
{
get {return base.GetValue(entitynameProperty).ToString(); }
set { base.SetValue(entitynameProperty, value); }
}
public static DependencyProperty attributenameProperty = DependencyProperty.Register("attributename", typeof(string), typeof(WorkflowQuery));
[CrmInput("Attribute To Retrieve")]
[CrmDefault("statuscode")]
public string attributename
{
get { return base.GetValue(attributenameProperty).ToString(); }
set { base.SetValue(attributenameProperty, value); }
}
public static DependencyProperty conditionattributenameProperty = DependencyProperty.Register("conditionattributename", typeof(string), typeof(WorkflowQuery));
[CrmInput("Condition attribute")]
[CrmDefault("name")]
public string conditionattributename
{
get { return base.GetValue(conditionattributenameProperty).ToString(); }
set { base.SetValue(conditionattributenameProperty, value); }
}
public static DependencyProperty conditionvalueProperty = DependencyProperty.Register("conditionvalue", typeof(string), typeof(WorkflowQuery));
[CrmInput("Condition value")]
[CrmDefault("Example account name")]
public string conditionvalue
{
get { return base.GetValue(conditionvalueProperty).ToString(); }
set { base.SetValue(conditionvalueProperty, value); }
}
public static DependencyProperty resultProperty = DependencyProperty.Register("result", typeof(string), typeof(WorkflowQuery));
[CrmOutput("Result")]
[CrmDefault("")]
public string result
{
get { return base.GetValue(resultProperty).ToString(); }
set { base.SetValue(resultProperty, value); }
}
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
IContextService contextService = (IContextService)executionContext.GetService(typeof(IContextService));
IWorkflowContext context = contextService.Context;

ICrmService crmService = context.CreateCrmService();

QueryExpression query = new QueryExpression();

query.EntityName = this.entityname;

ColumnSet columns = new ColumnSet();
columns.Attributes.Add(this.attributename);
query.ColumnSet = columns;


query.Criteria = new FilterExpression();
query.Criteria.FilterOperator = LogicalOperator.And;

ConditionExpression condition1 = new ConditionExpression();
condition1.AttributeName = this.conditionattributename;
condition1.Operator = ConditionOperator.Equal;
condition1.Values = new object[] { this.conditionvalue};

query.Criteria.Conditions.Add(condition1);

RetrieveMultipleRequest retrieve = new RetrieveMultipleRequest();
retrieve.Query = query;
retrieve.ReturnDynamicEntities = true;
RetrieveMultipleResponse retrieved = (RetrieveMultipleResponse)crmService.Execute(retrieve);

DynamicEntity entity = (DynamicEntity)retrieved.BusinessEntityCollection.BusinessEntities[0];

if (entity.Properties[this.attributename] is Status)
{
Status prop = (Status)entity.Properties[this.attributename];
this.result = prop.name.ToString();
}
else if (entity.Properties[this.attributename] is CrmNumber)
{
CrmNumber prop = (CrmNumber)entity.Properties[this.attributename];
this.result = prop.Value.ToString();
}
else if (entity.Properties[this.attributename] is CrmDateTime)
{
CrmDateTime prop = (CrmDateTime)entity.Properties[this.attributename];
this.result = prop.Value;
}
else if (entity.Properties[this.attributename] is CrmMoney)
{
CrmMoney prop = (CrmMoney)entity.Properties[this.attributename];
this.result = prop.Value.ToString();
}
else if (entity.Properties[this.attributename] is Key)
{
Key prop = (Key)entity.Properties[this.attributename];
this.result = prop.Value.ToString();
}
else if (entity.Properties[this.attributename] is Picklist)
{
Picklist prop = (Picklist)entity.Properties[this.attributename];
this.result = prop.name;
}
else if (entity.Properties[this.attributename] is Owner)
{
Owner prop = (Owner)entity.Properties[this.attributename];
this.result = prop.Value.ToString();
}
else //stringproperty
{
String prop = (String)entity.Properties[this.attributename];
this.result = prop;
}
return ActivityExecutionStatus.Closed;

}
}


This code will first build your query by setting the name of the entity to be queried, and the attribute you wish to retrieve. Then you have to specify a condition attribute and a value for that.

The activity will the use this information to generate a query object, which in turn is queried and the results retrieved as dynamic entitties. The attribute of the entity you wish to retrieve is the checked for type and cast appropriatly in order to set the value for the output property, result.

You can use this activity in your workflow, and incorporate the result in other workflow activities.

A potential development of this activity could be support for linked entities, ability to specify the type of the output property and reference target (Such as lookup for account, for instance), and perhaps a special variety for those dreadful N:N - relationships.

No comments:

Post a Comment