Wednesday, May 11, 2011

How to: Use Associate Requests to Associate Entities Using Jscript and .NET in Microsoft Dynamics CRM

This illustration shows you how to associate two entities in Microsoft Dynamics CRM 2011 using the associate request against a known relationship.   This example will be given in SOAP (JScript) and in C# (.NET) with the Associate message against the CRM 2011 organization service.  In this example we are simply associating a contact to an account as it's parent customer using the existing "contact_customer_accounts" relationship in CRM.

You can find the relationship schema name to use in the request by going to settings - customization - your entity - relationships.  Here is an example of where to find it.


Ok, here is what the calls look like!
First in C#:

AssociateRequest areq = new AssociateRequest();

         //Target is the entity that you are associating your entities to.
         areq.Target = new EntityReference("account", new Guid("28B942AE-0B70-E011-8DD8-1CC1DEE8EA59"));

         //RelatedEntities are the entities you are associating to your target (can be more than 1)
         areq.RelatedEntities = new EntityReferenceCollection();
         areq.RelatedEntities.Add(new EntityReference("contact", new Guid("5AC04904-D57B-E011-941A-1CC1DEE8EA59")));

         //The relationship schema name in CRM you are using to associate the entities. 
         //found in settings - customization - entity - relationships
         areq.Relationship = new Relationship("contact_customer_accounts");

         //execute the request
         service.Execute(areq);

If you need help instantiating a service object in .NET check out this post:
http://mileyja.blogspot.com/2011/04/instantiating-service-object-within.html


Now here is the Jscript nicely formatted by the CRM 2011 SOAP formatter. Available at: http://crm2011soap.codeplex.com/

Now in Jscript:

if (typeof (SDK) == "undefined")
   { SDK = { __namespace: true }; }
       //This will establish a more unique namespace for functions in this library. This will reduce the 
       // potential for functions to be overwritten due to a duplicate name when the library is loaded.
       SDK.SAMPLES = {
           _getServerUrl: function () {
               ///<summary>
               /// Returns the URL for the SOAP endpoint using the context information available in the form
               /// or HTML Web resource.
               ///</summary>
               var OrgServicePath = "/XRMServices/2011/Organization.svc/web";
               var serverUrl = "";
               if (typeof GetGlobalContext == "function") {
                   var context = GetGlobalContext();
                   serverUrl = context.getServerUrl();
               }
               else {
                   if (typeof Xrm.Page.context == "object") {
                         serverUrl = Xrm.Page.context.getServerUrl();
                   }
                   else
                   { throw new Error("Unable to access the server URL"); }
                   }
                  if (serverUrl.match(/\/$/)) {
                       serverUrl = serverUrl.substring(0, serverUrl.length - 1);
                   } 
                   return serverUrl + OrgServicePath;
               }, 
           AssociateRequestRequest: function () {
               var requestMain = ""
               requestMain += "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
               requestMain += "  <s:Body>";
               requestMain += "    <Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
               requestMain += "      <request i:type=\"a:AssociateRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\">";
               requestMain += "        <a:Parameters xmlns:b=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
               requestMain += "          <a:KeyValuePairOfstringanyType>";
               requestMain += "            <b:key>Target</b:key>";
               requestMain += "            <b:value i:type=\"a:EntityReference\">";
               requestMain += "              <a:Id>28b942ae-0b70-e011-8dd8-1cc1dee8ea59</a:Id>";
               requestMain += "              <a:LogicalName>account</a:LogicalName>";
               requestMain += "              <a:Name i:nil=\"true\" />";
               requestMain += "            </b:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "          <a:KeyValuePairOfstringanyType>";
               requestMain += "            <b:key>Relationship</b:key>";
               requestMain += "            <b:value i:type=\"a:Relationship\">";
               requestMain += "              <a:PrimaryEntityRole i:nil=\"true\" />";
               requestMain += "              <a:SchemaName>contact_customer_accounts</a:SchemaName>";
               requestMain += "            </b:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "          <a:KeyValuePairOfstringanyType>";
               requestMain += "            <b:key>RelatedEntities</b:key>";
               requestMain += "            <b:value i:type=\"a:EntityReferenceCollection\">";
               requestMain += "              <a:EntityReference>";
               requestMain += "                <a:Id>5ac04904-d57b-e011-941a-1cc1dee8ea59</a:Id>";
               requestMain += "                <a:LogicalName>contact</a:LogicalName>";
               requestMain += "                <a:Name i:nil=\"true\" />";
               requestMain += "              </a:EntityReference>";
               requestMain += "            </b:value>";
               requestMain += "          </a:KeyValuePairOfstringanyType>";
               requestMain += "        </a:Parameters>";
               requestMain += "        <a:RequestId i:nil=\"true\" />";
               requestMain += "        <a:RequestName>Associate</a:RequestName>";
               requestMain += "      </request>";
               requestMain += "    </Execute>";
               requestMain += "  </s:Body>";
               requestMain += "</s:Envelope>";
               var req = new XMLHttpRequest();
               req.open("POST", SDK.SAMPLES._getServerUrl(), true)
               // Responses will return XML. It isn't possible to return JSON.
               req.setRequestHeader("Accept", "application/xml, text/xml, */*");
               req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
               req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
               var successCallback = null;
               var errorCallback = null;
               req.onreadystatechange = function () { SDK.SAMPLES.AssociateRequestResponse(req, successCallback, errorCallback); };
               req.send(requestMain);
           },
       AssociateRequestResponse: function (req, successCallback, errorCallback) {
               ///<summary>
               /// Recieves the assign response
               ///</summary>
               ///<param name="req" Type="XMLHttpRequest">
               /// The XMLHttpRequest response
               ///</param>
               ///<param name="successCallback" Type="Function">
               /// The function to perform when an successfult response is returned.
               /// For this message no data is returned so a success callback is not really necessary.
               ///</param>
               ///<param name="errorCallback" Type="Function">
               /// The function to perform when an error is returned.
               /// This function accepts a JScript error returned by the _getError function
               ///</param>
               if (req.readyState == 4) {
               if (req.status == 200) {
               if (successCallback != null)
               { successCallback(); }
               }
               else {
                   errorCallback(SDK.SAMPLES._getError(req.responseXML));
               }
           }
       },
       _getError: function (faultXml) {
           ///<summary>
           /// Parses the WCF fault returned in the event of an error.
           ///</summary>
           ///<param name="faultXml" Type="XML">
           /// The responseXML property of the XMLHttpRequest response.
           ///</param>
           var errorMessage = "Unknown Error (Unable to parse the fault)";
           if (typeof faultXml == "object") {
               try {
                   var bodyNode = faultXml.firstChild.firstChild;
                   //Retrieve the fault node
                   for (var i = 0; i < bodyNode.childNodes.length; i++) {
                       var node = bodyNode.childNodes[i];
                       //NOTE: This comparison does not handle the case where the XML namespace changes
                       if ("s:Fault" == node.nodeName) {
                       for (var j = 0; j < node.childNodes.length; j++) {
                           var faultStringNode = node.childNodes[j];
                           if ("faultstring" == faultStringNode.nodeName) {
                               errorMessage = faultStringNode.text;
                               break;
                           }
                       }
                       break;
                   }
               }
           }
           catch (e) { };
        }
        return new Error(errorMessage);
     },
 __namespace: true
};




To understand how to parse the response please review my post on using the DOM parser.
Now you can call the SDK.SAMPLES.AssociateRequestRequest function from your form jscript handler.  Yes I realize it says request twice, I made a typo in the Soap Formatter tool :)
Thats all there is to it!

I hope this helps!

7 comments:

  1. This is almost exactly what I'm trying to do but it doesn't work in my case. I'm trying to setthe parent account of and account and getting the exception "Entity role must be specified for reflexive relationship 'account_parent_account.'" I can't find anything about an "entity role". Any ideas?

    Thanks in advance,
    Bill

    ReplyDelete
  2. Evidently something needs to be done differently if it is a relationship where the entity types are the same. There isn't a lot of good data on this one. I would ask a question in the forums maybe. I might look into this in the next couple days if I have time.

    ReplyDelete
  3. I found my answer. I added this.

    areq.Relationship.PrimaryEntityRole = EntityRole.Referenced;

    Bill

    ReplyDelete
  4. Hi, Jamie.

    How to check if there's existing association between records? Or what will the reqeust do, if an association already exists?

    Thanks.

    ReplyDelete
  5. There are a number of things you can do. If you retrieve the metadata for an entity it can show you if there is a relationship between entity types. You can find how to do that here: http://mileyja.blogspot.com/2011/05/how-to-retrieve-metadata-for-entity.html

    You can also then use the relationship schema name of a one to many relationship to determine if there are associated entities based on a relationship using this methodology in this link. Note that I give two examples in this post and the second one contains the methodology you are looking for to return related entities based on a relationship: http://mileyja.blogspot.com/2011/04/how-to-use-retrieve-messages-in-jscript.html

    ReplyDelete
  6. Hi,
    I am having issue,
    My Requirement is, need to assign default Role to newly created User. for the same
    i have plugin on Post event of new user creation, and while new user gets created , on post event my plugin gets executed, thats having the associateRequest code, but the errors i am getting is User does not exist. even the plugin is on Post create event.

    ReplyDelete