Friday, February 17, 2012

Plugin to update children records when parent record is updated

I love the developer's toolkit. It makes it so easy to deploy plugins. In this blog we are going to create a plugin that will update the Main Phone (“telephone1”) of the contacts when the Main Phone of their parent account is updated. We will use the OrganizationContext to update the contacts.
  1. Create a new solution in CRM2011. I named my solution “CRM Plugin Solution”.
  2. Open Visual Studio 2010. Select File—New –Project. It will display new project templates dialog as shown in the screen sheet belowp1
  3. Select “Dynamics CRM 2011 Package” project. This is also optional. You can go ahead and select “Dynamics CRM 2011 Plugin Library”, but then you cannot deploy the plugin straight from the Visual Studio. You have to use Plugin Registration tool to register the plugin. Enter the name of project/solution.
  4. VS studio will display following dialog. Enter you CRM 2011 server details. Select the solution name we created in step 1 and click okp2
  5. Now right click on the solution and add “Dynamics CRM 2011 Plugin Library” project to the solution. The plugin project will already have a plugin.cs file.
  6. Now sign the plugin assembly. Right click on Plugin Project and select properties. Select Signing tab from left navigation of project property page. On the Signing tab, select the Sign the assembly check box and set the strong name key file of your choice. At a minimum, you must specify a new key file name. Do not protect your key file by using a password.
  7. If you cannot see the “CRM Explorer” window on the upper left side of the VS studio, click on View menu and select “CRM Explorer”. p3
  8. Now expand “Entities” Node. Right Click on “Account” entity and select “Create Plugin”.p4
  9. It will display a following screen. It is equivalent to “Update Step” screen in plugin registration tool. Notice the  “Pre Image Alias” and  “Post Image Alias”. I have passed the “telephone1” attribute to the pre image and post image. You can change of the Class attribute. Press Ok.image_thumb[7]
  10. It will create a PostAccountUpdateContacts.cs file with name mentioned in “Class” attribute in screen shot above. Double click on the class file (PostAccountUpdateContacts.cs).
  11. Add the following references at the top
  12.  using Microsoft.Xrm.Sdk.Client; // to get the OrganizationContext
     using System.Linq; // to use linq queries with OrganizationContext
    
  13. Scroll down the PostAccountUpdateContacts.cs file and look for ExecutePostAccountUpdateContacts method. Overwrite the method with the following code. Look at the comments to understand the code
  14.  protected void ExecutePostAccountUpdateContacts(LocalPluginContext localContext)
    {
        if (localContext == null)
        {
            throw new ArgumentNullException("localContext");
        }
        string oldPhone = ""; // to store the old Main Phone no:
        string newPhone = ""; //// to store the new Main Phone no:
    
        // get the plugin context 
        IPluginExecutionContext context = localContext.PluginExecutionContext;
    
        //Get the IOrganizationService
        IOrganizationService service = localContext.OrganizationService;
              
        //create the service context
        var ServiceContext = new OrganizationServiceContext(service);
        ITracingService tracingService = localContext.TracingService;
    
        // The InputParameters collection contains all the data passed in the message request.
        if (context.InputParameters.Contains("Target") &&
        context.InputParameters["Target"] is Entity)
        {
            // Obtain the target entity from the input parmameters.
            Entity entity = (Entity)context.InputParameters["Target"];
    
            // get the pre entity image
            Entity preImageEntity = (context.PreEntityImages != null && context.PreEntityImages.Contains(this.preImageAlias)) ? context.PreEntityImages[this.preImageAlias] : null;
            
            // get the post entity image
            Entity postImageEntity = (context.PostEntityImages != null && context.PostEntityImages.Contains(this.postImageAlias)) ? context.PostEntityImages[this.postImageAlias] : null;
    
            // get the preimage and postimage telephone1 value
            if (preImageEntity.Attributes.Contains("telephone1"))
            {
                oldPhone = (string)preImageEntity.Attributes["telephone1"];
            }
    
            if (postImageEntity.Attributes.Contains("telephone1"))
            {
                newPhone = (string)postImageEntity.Attributes["telephone1"];
            }
    
            if (newPhone != oldPhone)
            {
                try
                {
                    //Create query to get the related contacts
                    var res = from c in ServiceContext.CreateQuery("contact")
                                where c["parentcustomerid"].Equals(entity.Id)
                                select c;
    
                    foreach (var c in res)
                    {
                        Entity e = (Entity)c;
                        e["telephone1"] = newPhone;
    
                        //ServiceContext.Attach(e);
                        ServiceContext.UpdateObject(e);
                    }
    
                    ServiceContext.SaveChanges();
    
    
                }
                catch (FaultException ex)
                {
                    throw new InvalidPluginExecutionException("An error occurred in the plug-in.", ex);
                }
            }
    
        }
    }
    
  15. Now right click on CRM Package project we created in step 2 and select deploy. It will register the plugin assembly as well as step, preEntityImage and postEntityImage for the plugin. 
  16. Update the “Main Phone” on account entity and test the plugin.

14 comments:

  1. I am having a problem. When I update the phone number in the Main account entity, it updates a the phone number in contact entity with previous data. Ex. if old data was 222555 and I changed it to 000000, it will update contact entity's phone number with 222555. I followed all the steps along with sync plugin and Post Operation.

    ReplyDelete
    Replies
    1. Have a look, you may be using the preimage value instead of postimage value.

      Delete
    2. The field name for my preimage value and postimage value are same. Could that be the reason? 'telephone1' field in your example is also the same. Does it make sense?

      Delete
    3. Solved. I was saving newvalue in preimage entity and hence the issue. Thanks for your help!

      Delete
    4. Btw, thanks for awesome stuff!

      Delete
  2. I followed your instructions except I'm running the my plugin from the contact entity.
    When a contact record is updated, the plugin will update other contacts which have the same email as the one that was initially updated. I have my code set up just like yours except for the query and my variables/fields I'm using.
    After I deploy and try to update a contact record, the other contact records are not being updated.
    Any ideas what could be wrong?

    Thanks

    ReplyDelete
  3. Someone told me to check this blog out and it might help me with my situation. I'm trying to figure out how can this solve my current issue. Here's the situation:

    In my Project entity, I've added a field named "Total Allocated Hrs".

    My Task entity contains a "Time Allocated" field. I must be able to add several Tasks in a Project, that's why it's a 1:N relationship.

    Now in the Details panel (on the left) of Project, I've got a Project Resource tab and I can add several Tasks.

    What I want to do is that each time I add a Task (with Time Allocated) for that Project , the "Total Allocated Hrs" field needs to get updated with the sum of all the "Time Allocated" for all Tasks in that Project.

    The problem is that I don't know how to update this "Total Allocated Hrs" with field values which are not from the same form ie. multiple Task forms.

    Is there any way to do it with a JScript or a workflow (or any other way) that goes through every Task and adds its "Time Allocated" value to the "Total Allocated Hrs" ? I'd like to do advanced find, views etc on the hours.

    Greatly appreciated!

    ReplyDelete
  4. Thanks for this tuto !

    I'm now trying something else:
    when resolving an incident, we try to save time spent and modify the contract line
    For this, we created 3 new fields in contractdetail table
    Then, i created a pluggin, with message create, on post-operation

    here is my code:

    //TODO: Implement your custom Plug-in business logic.
    IPluginExecutionContext context = localContext.PluginExecutionContext;
    IOrganizationService service = localContext.OrganizationService;

    var ServiceContextIncident = new OrganizationServiceContext(service);
    var ServiceContextLContrats = new OrganizationServiceContext(service);
    ITracingService tracingService = localContext.TracingService;

    if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
    {
    Entity entity = (Entity)context.InputParameters["Target"];

    try
    {
    var resultatIncidents = from i in ServiceContextIncident.CreateQuery("incident")
    where i["incidentid"].Equals(entity.Attributes["incidentid"])
    select i;

    foreach (var i in resultatIncidents)
    {
    Entity MonIncident = (Entity)i;

    var resultatLignesDeContrat = from lc in ServiceContextLContrats.CreateQuery("contractdetail")
    where lc["contractdetailid"].Equals(MonIncident.Attributes["contractdetailid"])
    select lc;

    foreach (var lc in resultatLignesDeContrat)
    {

    Entity MaLigneDeContrat = (Entity)lc;

    int tpspasse = int.Parse(MaLigneDeContrat.Attributes["new_tpspasse"].ToString());
    tpspasse += int.Parse(entity.Attributes["timespent"].ToString());
    MaLigneDeContrat.Attributes["new_TpsPasse"] = tpspasse;

    int tpsrestant = int.Parse(MaLigneDeContrat.Attributes["new_tpsrestant"].ToString());
    tpsrestant -= int.Parse(entity.Attributes["timespent"].ToString());
    MaLigneDeContrat.Attributes["new_tpsrestant"] = tpsrestant;

    ServiceContextLContrats.UpdateObject(MaLigneDeContrat);

    }

    ServiceContextLContrats.SaveChanges();

    }

    }

    catch (FaultException ex)
    {
    throw new InvalidPluginExecutionException("Erreur plugin. Detail: ", ex);
    }

    }

    When resolving an incident, associated to a customer, a contract and a contract line, i always get the same error:

    Unhandled Exception: System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault, Microsoft.Xrm.Sdk, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]: Unexpected exception from plug-in (Execute): Merckel.Incidents.CreationResolutionIncident: Microsoft.Xrm.Sdk.SaveChangesException: An error occured while processing this request.Detail:

    -2147220956


    anny idea ?

    ReplyDelete
  5. I'm trying to replicate this plugin with the exact same entities but i'm getting this error. I'm trying to implement this for CRM 2013 on premise.

    [TestPluginUpdateContact.Plugins: TestPluginUpdateContact.Plugins.PostClientUpdate]
    [f7745ed0-a9f8-e311-8b1d-0050568b2304: PostClientUpdate]

    Entered TestPluginUpdateContact.Plugins.PostClientUpdate.Execute(), Correlation Id: 03512805-6b7c-4da3-b7bd-ff106a3deae1, Initiating User: a962f321-f276-e211-9b80-0050568b2304
    TestPluginUpdateContact.Plugins.PostClientUpdate is firing for Entity: account, Message: Update, Correlation Id: 03512805-6b7c-4da3-b7bd-ff106a3deae1, Initiating User: a962f321-f276-e211-9b80-0050568b2304
    Exiting TestPluginUpdateContact.Plugins.PostClientUpdate.Execute(), Correlation Id: 03512805-6b7c-4da3-b7bd-ff106a3deae1, Initiating User: a962f321-f276-e211-9b80-0050568b2304



    ReplyDelete
    Replies
    1. Nevermind, i figured it out.

      Delete
    2. what was the issue and how did you resolve it. Will be useful for others who encounter similar exceptions.

      Delete