I posted an earlier article on this topic, based on a question I got from long-time Trick Bag reader Sherry Hale. She wanted to know if you can use an automatic workflow to push changes to an account record down to all of the child contact records. I said,
- Unfortunately, you can’t
- But you can do it with an On Demand workflow on contacts, if you don’t mind manually selecting all of the contact records and clicking the Run Workflow button
- You can also do it with code. I hadn’t done it before but I’d seen articles about it, knew it was out there somewhere, and asked if anybody had any good examples please send them along.
A few hours after posting the article, I received an email from Jim Steger. Jim is a principle with Sonoma Partners and co-author of two of my three favorite books on Dynamics CRM, Working with Dynamics CRM 4.0, and Programming Dynamics CRM 4.0. He reminded me of an example he’d included in his Working with book, and graciously consented to my posting the note along with the code. (I say “reminded” because his was the example I knew was out there somewhere and I’d just got done scouring the Programming book looking for it.)
So with that, below is the note and the code to push changes made to a parent record down to child records, using Accounts and Contacts as an example.
Jim: thank you so much!
Sherry: if you have questions about the code, ask Jim! J
*******
Hi Richard-
I wrote an example in my Working with Dynamics CRM 4.0 book that does something very similar in code to the request in your recent blog post. I did have to use a plug-in though and I used a custom attribute on the contact that would determine whether it should be sync’d with the parent.
You can download the code from Microsoft here:
http://www.microsoft.com/mspress/companion/9780735623781/
Regards-
Jim
It is not the simplest of code and might be optimized, but you should get the idea.
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.Services.Protocols;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.Sdk.Query;
namespace WorkingWithDynamicsCrm4.Plugin
{
public class AddressSync : IPlugin
{
/// <summary>
/// This sample synchronizes a contacts address with its parent account asynchronously.
/// </summary>
public void Execute(IPluginExecutionContext context)
{
// Verify we have an entity to work with
if (context.InputParameters.Properties.Contains("Target") && context.InputParameters.Properties["Target"] is DynamicEntity)
{
ICrmService service = context.CreateCrmService(false);
// Obtain the target business entity from the input parmameters.
DynamicEntity entity = (DynamicEntity)context.InputParameters.Properties["Target"];
// Verify that the entity represents an account.
if (entity.Name == EntityName.account.ToString())
{
Dictionary<string, Property> changedValues = new Dictionary<string, Property>();
changedValues = SetChangedAddressValues(entity);
// if we have a changed address value, then continue with the update
if (changedValues.Count > 0)
{
Guid accountId = ((Key)entity.Properties["accountid"]).Value;
// retrieve applicable contact ids
BusinessEntityCollection contactsToUpdate = RetrieveContactsForAddressSync(service, accountId);
// update all contacts found
UpdateContactAddress(service, contactsToUpdate, changedValues);
}
}
}
}
/// <summary>
/// Stores address values.
/// </summary>
/// <param name="entity">DynamicEntity object.</param>
/// <returns></returns>
private Dictionary<string, Property> SetChangedAddressValues(DynamicEntity entity)
{
Dictionary<string, Property> changedValues = new Dictionary<string, Property>();
AddStringPropertyToDictionary(entity, "address1_name", changedValues);
AddStringPropertyToDictionary(entity, "address1_line1", changedValues);
AddStringPropertyToDictionary(entity, "address1_line2", changedValues);
AddStringPropertyToDictionary(entity, "address1_line3", changedValues);
AddStringPropertyToDictionary(entity, "address1_city", changedValues);
AddStringPropertyToDictionary(entity, "address1_address1_stateorprovincename", changedValues);
AddStringPropertyToDictionary(entity, "address1_postalcode", changedValues);
AddStringPropertyToDictionary(entity, "address1_country", changedValues);
AddStringPropertyToDictionary(entity, "address1_telephone1", changedValues);
AddPicklistPropertyToDictionary(entity, "address1_addresstypecode", changedValues);
AddPicklistPropertyToDictionary(entity, "address1_shippingmethodcode", changedValues);
AddPicklistPropertyToDictionary(entity, "address1_freighttermscode", changedValues);
return changedValues;
}
/// <summary>
/// Updates the contact record's address information based on the new values from the account.
/// </summary>
/// <param name="service">CRM service object.</param>
/// <param name="contactsToUpdate">Collection of contacts to update.</param>
/// <param name="newAddressValues">Dictionary of address values</param>
private void UpdateContactAddress(ICrmService service, BusinessEntityCollection contactsToUpdate, Dictionary<string,Property> newAddressValues)
{
// Loop through each contact in the collection
foreach (DynamicEntity retrievedContacts in contactsToUpdate.BusinessEntities)
{
try
{
DynamicEntity oContact = new DynamicEntity();
oContact.Name = EntityName.contact.ToString();
KeyProperty contactId = new KeyProperty();
contactId.Name = "contactid";
contactId.Value = ((Key)retrievedContacts.Properties["contactid"]);
oContact.Properties.Add(contactId);
// Loop through address dictionary and add the address values as properties to the contact
foreach (KeyValuePair<string, Property> prop in newAddressValues)
{
oContact.Properties.Add(prop.Value);
}
// Update the contact
TargetUpdateDynamic target = new TargetUpdateDynamic();
target.Entity = oContact;
UpdateRequest update = new UpdateRequest();
update.Target = target;
UpdateResponse response = (UpdateResponse)service.Execute(update);
}
catch (SoapException ex)
{
throw new Exception(ex.Detail.InnerXml.ToString());
}
}
}
/// <summary>
/// Returns all contacts from the account valid for address synchronization.
/// </summary>
/// <param name="service">CRM service object.</param>
/// <param name="accountId">The identifier of the account.</param>
/// <returns></returns>
private BusinessEntityCollection RetrieveContactsForAddressSync(ICrmService service, Guid accountId)
{
// Set up standard query expression
ColumnSet cols = new ColumnSet();
cols.AddColumns(new string[] { "contactid" });
QueryByAttribute query = new QueryByAttribute();
query.EntityName = "contact";
query.Attributes = new String[] { "parentcustomerid", "sonoma_syncaddresswithparent" };
query.Values = new Object[] { accountId, true };
query.ColumnSet = cols;
try
{
// Retrieve the values from CRM as a dynamic entity
RetrieveMultipleRequest request = new RetrieveMultipleRequest();
request.ReturnDynamicEntities = true;
request.Query = query;
RetrieveMultipleResponse matchingContacts = (RetrieveMultipleResponse)service.Execute(request);
return matchingContacts.BusinessEntityCollection;
}
catch (SoapException ex)
{
throw new Exception(ex.Detail.InnerText);
}
}
/// <summary>
/// Helper method to add string properties to the dictionary
/// </summary>
/// <param name="entity">DynamicEntity to evaluate.</param>
/// <param name="attribute">Attribute name.</param>
/// <param name="newValue">Attribut value.</param>
private void AddStringPropertyToDictionary(DynamicEntity entity, string attribute, Dictionary<string, Property> newValue)
{
if (entity.Properties.Contains(attribute))
{
StringProperty prop = new StringProperty();
prop.Name = attribute;
prop.Value = entity.Properties[attribute].ToString();
newValue[attribute] = prop;
}
}
/// <summary>
/// Helper method to add picklist properties to the dictionary
/// </summary>
/// <param name="entity">DynamicEntity to evaluate.</param>
/// <param name="attribute">Attribute name.</param>
/// <param name="newValue">Attribut value.</param>
private void AddPicklistPropertyToDictionary(DynamicEntity entity, string attribute, Dictionary<string, Property> newValue)
{
if (entity.Properties.Contains(attribute))
{
Picklist picklist = new Picklist();
picklist.name = attribute;
picklist.Value = ((Picklist)entity.Properties[attribute]).Value;
PicklistProperty prop = new PicklistProperty();
prop.Name = attribute;
prop.Value = picklist;
newValue[attribute] = prop;
}
}
}
}