locked
How to stop a field having the same data stored in multiple records RRS feed

  • Question

  • Hi

    I'm currently developing Microsoft CRM plugins for a company

    My question today is to do with actual CRM Dynamics and possibly Javascript

    My problem is that I have chosen a field for example, lets call it Account Code, I want Account Code to be the unique identifier for each record that exists in the Entity Account.

    To do this it's to my understanding that I would have to access the SQL Database that all records are stored too to check if the Account Code already exists when trying to save a new record

    Does anyone know how this is done?

    Thanks, Shaun

    Tuesday, June 3, 2014 2:56 PM

Answers

  • Hi Shaun,

    Although the data in CRM is stored in SQL Server database, it's not needed to access the database itself to carry out any CRUD operation. In fact, Microsoft doesn't recommend that at all. There are other ways to access data - using the web service api's (SOAP and REST) exposed by CRM and if you are using Plugins, you will automatically get access to the CRM Service object which you can then use to call methods like Create, Update, RetreiveMultiple, etc.

    Now, to provide a solution to your problem. There are two ways of achieving the Account Code requirement:

    a. Use JavaScript : Using JavaScript, you can check "On Save" of an Account record if any other Account record exists (REST api in CRM allows you to fetch records using JavaScript by specifying a filter criteria) with the same Account Code and prevent save.

    b. Use Plugin : You can have a synchronous Plugin registered on Create/Update of Account entity and check if there is any other Account in CRM with the same Account Code and prevent it from saving by throwing an uncaught exception (generally the InvalidPluginExecution).

    Using the JavaScript makes it light weight since it's client side, but it will only be effective for Account record created from the CRM interface; records created in other ways (external apps, web services, other plugins, etc.) won't go through this validation. Using Plugins makes sure any record created in CRM by any means goes through the validation and is hence better.


    Admin QuikView Solution for CRM 2013

    Wednesday, June 4, 2014 4:25 AM
  • Hi Shaun,

    First you might want to download the CRM 2013 SDK : http://www.microsoft.com/en-in/download/details.aspx?id=40321

    Once you have done that, open up the sample code in this location "\SDK\SampleCode\JS\RESTEndpoint\JavaScriptRESTRetrieveMultiple" to understand how the call is made to CRM.

    Secondly, download the CRM OData Query Designer (which helps you to form the query easily) : http://crm2011odatatool.codeplex.com/. Import it to CRM (its a managed solution, so it wont mess with other stuff).

    The query that you'd ideally be using would be something like the following:

    http://your_server_name:port_number/XRMServices/2011/OrganizationData.svc/$select=AccountId&$filter=AccountCode eq 'account_code_entered_on_form'

    This would give you any Account in CRM with that Account Code. You can simply check if there is a result (which means user is entering a duplicate Account Number), if not it's the first time. No need to iterate and check; becomes a lot easier.


    Admin QuikView Solution for CRM 2013

    Wednesday, June 4, 2014 7:49 AM
  • Hi Shaun,

    To answer your second question, after you have formed the query and tested that it actually works (by clicking on Generate and Show Result in the CRM oData Query Designer); if you look into the SDK sample, you will find a section in the code that says

    var options = "$select=Name,Telephone1&$top=" + number;

    This is where you actually specify what to select, what would be the filter criteria and everything. This might help you understand : http://blogs.msdn.com/b/crm/archive/2011/03/02/using-odata-retrieve-in-microsoft-dynamics-crm-2011.aspx

    So, you need to modify the code as follows:

    var options = "$select=AccountId&$filter=AccountCode eq" + number_entered_in_the_form;

    The number entered in the form can be retrieved by using the code:

    Xrm.Page.getAttribute("account_code_field_name").getValue()


    Admin QuikView Solution for CRM 2013

    Wednesday, June 4, 2014 9:06 AM
  • Hi Shaun,

    Try this:

    function onSaveValidation(context)
    {
    	var inputAccountCode = Xrm.Page.getAttribute("new_accountcode").getValue();
    	var options = "$select=new_AccountCode&$filter=new_AccountCode eq" + inputAccountCode;
    
    	SDK.REST.retrieveMultipleRecords("Account", options, retrieveAccountsCallBack, function (error) { alert(error.message); }, null);
    }
    function retrieveAccountsCallBack(retrievedAccounts) 
    {
    	if (retrievedAccounts.length > 0)
    	{
    		Xrm.Utility.alertDialog("Account Code already exists); 
    		context.getEventArgs().preventDefault();
    	}
    }

    Make sure you also link the sdk.rest.js to the form library.

    Admin QuikView Solution for CRM 2013



    Wednesday, June 4, 2014 12:04 PM
  • Hi Shaun,

    You've already added the webresource under your "Form Libraries" section right? So, add the sdk.rest.js also over there along with your existing webresource that contains the On Save function.

    For the OnSaveValidation() function, when you're linking it with Form OnSave make sure you have checked the "Pass execution context as the first parameter" checkbox as shown here :


    Admin QuikView Solution for CRM 2013

    Thursday, June 5, 2014 8:25 AM
  • Hi Shaoun,

    Okay, updates here! I tried out the exact same thing and it works (just using a different field) and I'll provide the contents of all scripts used here.

    Content of SDK.REST.js

    // =====================================================================
    //  This file is part of the Microsoft Dynamics CRM SDK code samples.
    //
    //  Copyright (C) Microsoft Corporation.  All rights reserved.
    //
    //  This source code is intended only as a supplement to Microsoft
    //  Development Tools and/or on-line documentation.  See these other
    //  materials for detailed information regarding Microsoft code samples.
    //
    //  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
    //  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
    //  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
    //  PARTICULAR PURPOSE.
    // =====================================================================
    // <snippetSDKRESTJS>
    if (typeof (SDK) == "undefined")
    { SDK = { __namespace: true }; }
    SDK.REST = {
     _context: function () {
      ///<summary>
      /// Private function to the context object.
      ///</summary>
      ///<returns>Context</returns>
      if (typeof GetGlobalContext != "undefined")
      { return GetGlobalContext(); }
      else {
       if (typeof Xrm != "undefined") {
        return Xrm.Page.context;
       }
       else
       { throw new Error("Context is not available."); }
      }
     },
     _getClientUrl: function () {
      ///<summary>
      /// Private function to return the server URL from the context
      ///</summary>
      ///<returns>String</returns>
      var clientUrl = this._context().getClientUrl()
    
      return clientUrl;
     },
     _ODataPath: function () {
      ///<summary>
      /// Private function to return the path to the REST endpoint.
      ///</summary>
      ///<returns>String</returns>
      return this._getClientUrl() + "/XRMServices/2011/OrganizationData.svc/";
     },
     _errorHandler: function (req) {
      ///<summary>
      /// Private function return an Error object to the errorCallback
      ///</summary>
      ///<param name="req" type="XMLHttpRequest">
      /// The XMLHttpRequest response that returned an error.
      ///</param>
      ///<returns>Error</returns>
      //Error descriptions come from http://support.microsoft.com/kb/193625
      if (req.status == 12029)
      { return new Error("The attempt to connect to the server failed."); }
      if (req.status == 12007)
      { return new Error("The server name could not be resolved."); }
      var errorText;
      try
            { errorText = JSON.parse(req.responseText).error.message.value; }
      catch (e)
            { errorText = req.responseText }
    
      return new Error("Error : " +
            req.status + ": " +
            req.statusText + ": " + errorText);
     },
     _dateReviver: function (key, value) {
      ///<summary>
      /// Private function to convert matching string values to Date objects.
      ///</summary>
      ///<param name="key" type="String">
      /// The key used to identify the object property
      ///</param>
      ///<param name="value" type="String">
      /// The string value representing a date
      ///</param>
      var a;
      if (typeof value === 'string') {
       a = /Date\(([-+]?\d+)\)/.exec(value);
       if (a) {
        return new Date(parseInt(value.replace("/Date(", "").replace(")/", ""), 10));
       }
      }
      return value;
     },
     _parameterCheck: function (parameter, message) {
      ///<summary>
      /// Private function used to check whether required parameters are null or undefined
      ///</summary>
      ///<param name="parameter" type="Object">
      /// The parameter to check;
      ///</param>
      ///<param name="message" type="String">
      /// The error message text to include when the error is thrown.
      ///</param>
      if ((typeof parameter === "undefined") || parameter === null) {
       throw new Error(message);
      }
     },
     _stringParameterCheck: function (parameter, message) {
      ///<summary>
      /// Private function used to check whether required parameters are null or undefined
      ///</summary>
      ///<param name="parameter" type="String">
      /// The string parameter to check;
      ///</param>
      ///<param name="message" type="String">
      /// The error message text to include when the error is thrown.
      ///</param>
      if (typeof parameter != "string") {
       throw new Error(message);
      }
     },
     _callbackParameterCheck: function (callbackParameter, message) {
      ///<summary>
      /// Private function used to check whether required callback parameters are functions
      ///</summary>
      ///<param name="callbackParameter" type="Function">
      /// The callback parameter to check;
      ///</param>
      ///<param name="message" type="String">
      /// The error message text to include when the error is thrown.
      ///</param>
      if (typeof callbackParameter != "function") {
       throw new Error(message);
      }
     },
     createRecord: function (object, type, successCallback, errorCallback) {
      ///<summary>
      /// Sends an asynchronous request to create a new record.
      ///</summary>
      ///<param name="object" type="Object">
      /// A JavaScript object with properties corresponding to the Schema name of
      /// entity attributes that are valid for create operations.
      ///</param>
      ///<param name="type" type="String">
      /// The Schema Name of the Entity type record to create.
      /// For an Account record, use "Account"
      ///</param>
      ///<param name="successCallback" type="Function">
      /// The function that will be passed through and be called by a successful response. 
      /// This function can accept the returned record as a parameter.
      /// </param>
      ///<param name="errorCallback" type="Function">
      /// The function that will be passed through and be called by a failed response. 
      /// This function must accept an Error object as a parameter.
      /// </param>
      this._parameterCheck(object, "SDK.REST.createRecord requires the object parameter.");
      this._stringParameterCheck(type, "SDK.REST.createRecord requires the type parameter is a string.");
      this._callbackParameterCheck(successCallback, "SDK.REST.createRecord requires the successCallback is a function.");
      this._callbackParameterCheck(errorCallback, "SDK.REST.createRecord requires the errorCallback is a function.");
      var req = new XMLHttpRequest();
      req.open("POST", encodeURI(this._ODataPath() + type + "Set"), true);
      req.setRequestHeader("Accept", "application/json");
      req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
      req.onreadystatechange = function () {
       if (this.readyState == 4 /* complete */) {
        req.onreadystatechange = null;
        if (this.status == 201) {
         successCallback(JSON.parse(this.responseText, SDK.REST._dateReviver).d);
        }
        else {
         errorCallback(SDK.REST._errorHandler(this));
        }
       }
      };
      req.send(JSON.stringify(object));
     },
     retrieveRecord: function (id, type, select, expand, successCallback, errorCallback) {
      ///<summary>
      /// Sends an asynchronous request to retrieve a record.
      ///</summary>
      ///<param name="id" type="String">
      /// A String representing the GUID value for the record to retrieve.
      ///</param>
      ///<param name="type" type="String">
      /// The Schema Name of the Entity type record to retrieve.
      /// For an Account record, use "Account"
      ///</param>
      ///<param name="select" type="String">
      /// A String representing the $select OData System Query Option to control which
      /// attributes will be returned. This is a comma separated list of Attribute names that are valid for retrieve.
      /// If null all properties for the record will be returned
      ///</param>
      ///<param name="expand" type="String">
      /// A String representing the $expand OData System Query Option value to control which
      /// related records are also returned. This is a comma separated list of of up to 6 entity relationship names
      /// If null no expanded related records will be returned.
      ///</param>
      ///<param name="successCallback" type="Function">
      /// The function that will be passed through and be called by a successful response. 
      /// This function must accept the returned record as a parameter.
      /// </param>
      ///<param name="errorCallback" type="Function">
      /// The function that will be passed through and be called by a failed response. 
      /// This function must accept an Error object as a parameter.
      /// </param>
      this._stringParameterCheck(id, "SDK.REST.retrieveRecord requires the id parameter is a string.");
      this._stringParameterCheck(type, "SDK.REST.retrieveRecord requires the type parameter is a string.");
      if (select != null)
       this._stringParameterCheck(select, "SDK.REST.retrieveRecord requires the select parameter is a string.");
      if (expand != null)
       this._stringParameterCheck(expand, "SDK.REST.retrieveRecord requires the expand parameter is a string.");
      this._callbackParameterCheck(successCallback, "SDK.REST.retrieveRecord requires the successCallback parameter is a function.");
      this._callbackParameterCheck(errorCallback, "SDK.REST.retrieveRecord requires the errorCallback parameter is a function.");
    
      var systemQueryOptions = "";
    
      if (select != null || expand != null) {
       systemQueryOptions = "?";
       if (select != null) {
        var selectString = "$select=" + select;
        if (expand != null) {
         selectString = selectString + "," + expand;
        }
        systemQueryOptions = systemQueryOptions + selectString;
       }
       if (expand != null) {
        systemQueryOptions = systemQueryOptions + "&$expand=" + expand;
       }
      }
    
    
      var req = new XMLHttpRequest();
      req.open("GET", encodeURI(this._ODataPath() + type + "Set(guid'" + id + "')" + systemQueryOptions), true);
      req.setRequestHeader("Accept", "application/json");
      req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
      req.onreadystatechange = function () {
       if (this.readyState == 4 /* complete */) {
        req.onreadystatechange = null;
        if (this.status == 200) {
         successCallback(JSON.parse(this.responseText, SDK.REST._dateReviver).d);
        }
        else {
         errorCallback(SDK.REST._errorHandler(this));
        }
       }
      };
      req.send();
     },
     updateRecord: function (id, object, type, successCallback, errorCallback) {
      ///<summary>
      /// Sends an asynchronous request to update a record.
      ///</summary>
      ///<param name="id" type="String">
      /// A String representing the GUID value for the record to retrieve.
      ///</param>
      ///<param name="object" type="Object">
      /// A JavaScript object with properties corresponding to the Schema Names for
      /// entity attributes that are valid for update operations.
      ///</param>
      ///<param name="type" type="String">
      /// The Schema Name of the Entity type record to retrieve.
      /// For an Account record, use "Account"
      ///</param>
      ///<param name="successCallback" type="Function">
      /// The function that will be passed through and be called by a successful response. 
      /// Nothing will be returned to this function.
      /// </param>
      ///<param name="errorCallback" type="Function">
      /// The function that will be passed through and be called by a failed response. 
      /// This function must accept an Error object as a parameter.
      /// </param>
      this._stringParameterCheck(id, "SDK.REST.updateRecord requires the id parameter.");
      this._parameterCheck(object, "SDK.REST.updateRecord requires the object parameter.");
      this._stringParameterCheck(type, "SDK.REST.updateRecord requires the type parameter.");
      this._callbackParameterCheck(successCallback, "SDK.REST.updateRecord requires the successCallback is a function.");
      this._callbackParameterCheck(errorCallback, "SDK.REST.updateRecord requires the errorCallback is a function.");
      var req = new XMLHttpRequest();
    
      req.open("POST", encodeURI(this._ODataPath() + type + "Set(guid'" + id + "')"), true);
      req.setRequestHeader("Accept", "application/json");
      req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
      req.setRequestHeader("X-HTTP-Method", "MERGE");
      req.onreadystatechange = function () {
       if (this.readyState == 4 /* complete */) {
        req.onreadystatechange = null;
        if (this.status == 204 || this.status == 1223) {
         successCallback();
        }
        else {
         errorCallback(SDK.REST._errorHandler(this));
        }
       }
      };
      req.send(JSON.stringify(object));
     },
     deleteRecord: function (id, type, successCallback, errorCallback) {
      ///<summary>
      /// Sends an asynchronous request to delete a record.
      ///</summary>
      ///<param name="id" type="String">
      /// A String representing the GUID value for the record to delete.
      ///</param>
      ///<param name="type" type="String">
      /// The Schema Name of the Entity type record to delete.
      /// For an Account record, use "Account"
      ///</param>
      ///<param name="successCallback" type="Function">
      /// The function that will be passed through and be called by a successful response. 
      /// Nothing will be returned to this function.
      /// </param>
      ///<param name="errorCallback" type="Function">
      /// The function that will be passed through and be called by a failed response. 
      /// This function must accept an Error object as a parameter.
      /// </param>
      this._stringParameterCheck(id, "SDK.REST.deleteRecord requires the id parameter.");
      this._stringParameterCheck(type, "SDK.REST.deleteRecord requires the type parameter.");
      this._callbackParameterCheck(successCallback, "SDK.REST.deleteRecord requires the successCallback is a function.");
      this._callbackParameterCheck(errorCallback, "SDK.REST.deleteRecord requires the errorCallback is a function.");
      var req = new XMLHttpRequest();
      req.open("POST", encodeURI(this._ODataPath() + type + "Set(guid'" + id + "')"), true);
      req.setRequestHeader("Accept", "application/json");
      req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
      req.setRequestHeader("X-HTTP-Method", "DELETE");
      req.onreadystatechange = function () {
    
       if (this.readyState == 4 /* complete */) {
        req.onreadystatechange = null;
        if (this.status == 204 || this.status == 1223) {
         successCallback();
        }
        else {
         errorCallback(SDK.REST._errorHandler(this));
        }
       }
      };
      req.send();
    
     },
     retrieveMultipleRecords: function (type, options, successCallback, errorCallback, OnComplete) {
      ///<summary>
      /// Sends an asynchronous request to retrieve records.
      ///</summary>
      ///<param name="type" type="String">
      /// The Schema Name of the Entity type record to retrieve.
      /// For an Account record, use "Account"
      ///</param>
      ///<param name="options" type="String">
      /// A String representing the OData System Query Options to control the data returned
      ///</param>
      ///<param name="successCallback" type="Function">
      /// The function that will be passed through and be called for each page of records returned.
      /// Each page is 50 records. If you expect that more than one page of records will be returned,
      /// this function should loop through the results and push the records into an array outside of the function.
      /// Use the OnComplete event handler to know when all the records have been processed.
      /// </param>
      ///<param name="errorCallback" type="Function">
      /// The function that will be passed through and be called by a failed response. 
      /// This function must accept an Error object as a parameter.
      /// </param>
      ///<param name="OnComplete" type="Function">
      /// The function that will be called when all the requested records have been returned.
      /// No parameters are passed to this function.
      /// </param>
      this._stringParameterCheck(type, "SDK.REST.retrieveMultipleRecords requires the type parameter is a string.");
      if (options != null)
       this._stringParameterCheck(options, "SDK.REST.retrieveMultipleRecords requires the options parameter is a string.");
      this._callbackParameterCheck(successCallback, "SDK.REST.retrieveMultipleRecords requires the successCallback parameter is a function.");
      this._callbackParameterCheck(errorCallback, "SDK.REST.retrieveMultipleRecords requires the errorCallback parameter is a function.");
      this._callbackParameterCheck(OnComplete, "SDK.REST.retrieveMultipleRecords requires the OnComplete parameter is a function.");
    
      var optionsString;
      if (options != null) {
       if (options.charAt(0) != "?") {
        optionsString = "?" + options;
       }
       else
       { optionsString = options; }
      }
      var req = new XMLHttpRequest();
      req.open("GET", this._ODataPath() + type + "Set" + optionsString, false);
      req.setRequestHeader("Accept", "application/json");
      req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
      req.onreadystatechange = function () {
       if (this.readyState == 4 /* complete */) {
        req.onreadystatechange = null;
        if (this.status == 200) {
         var returned = JSON.parse(this.responseText, SDK.REST._dateReviver).d;
         successCallback(returned.results);
         if (returned.__next != null) {
          var queryOptions = returned.__next.substring((SDK.REST._ODataPath() + type + "Set").length);
          SDK.REST.retrieveMultipleRecords(type, queryOptions, successCallback, errorCallback, OnComplete);
         }
         else
         { OnComplete(); }
        }
        else {
         errorCallback(SDK.REST._errorHandler(this));
        }
       }
      };
      req.send();
     },
     associateRecords: function (parentId, parentType, relationshipName, childId, childType, successCallback, errorCallback) {
      this._stringParameterCheck(parentId, "SDK.REST.associateRecords requires the parentId parameter is a string.");
      ///<param name="parentId" type="String">
      /// The Id of the record to be the parent record in the relationship
      /// </param>
      ///<param name="parentType" type="String">
      /// The Schema Name of the Entity type for the parent record.
      /// For an Account record, use "Account"
      /// </param>
      ///<param name="relationshipName" type="String">
      /// The Schema Name of the Entity Relationship to use to associate the records.
      /// To associate account records as a Parent account, use "Referencedaccount_parent_account"
      /// </param>
      ///<param name="childId" type="String">
      /// The Id of the record to be the child record in the relationship
      /// </param>
      ///<param name="childType" type="String">
      /// The Schema Name of the Entity type for the child record.
      /// For an Account record, use "Account"
      /// </param>
      ///<param name="successCallback" type="Function">
      /// The function that will be passed through and be called by a successful response. 
      /// Nothing will be returned to this function.
      /// </param>
      ///<param name="errorCallback" type="Function">
      /// The function that will be passed through and be called by a failed response. 
      /// This function must accept an Error object as a parameter.
      /// </param>
      this._stringParameterCheck(parentType, "SDK.REST.associateRecords requires the parentType parameter is a string.");
      this._stringParameterCheck(relationshipName, "SDK.REST.associateRecords requires the relationshipName parameter is a string.");
      this._stringParameterCheck(childId, "SDK.REST.associateRecords requires the childId parameter is a string.");
      this._stringParameterCheck(childType, "SDK.REST.associateRecords requires the childType parameter is a string.");
      this._callbackParameterCheck(successCallback, "SDK.REST.associateRecords requires the successCallback parameter is a function.");
      this._callbackParameterCheck(errorCallback, "SDK.REST.associateRecords requires the errorCallback parameter is a function.");
    
      var req = new XMLHttpRequest();
      req.open("POST", encodeURI(this._ODataPath() + parentType + "Set(guid'" + parentId + "')/$links/" + relationshipName), true);
      req.setRequestHeader("Accept", "application/json");
      req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
      req.onreadystatechange = function () {
       if (this.readyState == 4 /* complete */) {
        req.onreadystatechange = null;
        if (this.status == 204 || this.status == 1223) {
         successCallback();
        }
        else {
         errorCallback(SDK.REST._errorHandler(this));
        }
       }
      };
      var childEntityReference = {}
      childEntityReference.uri = this._ODataPath() + "/" + childType + "Set(guid'" + childId + "')";
      req.send(JSON.stringify(childEntityReference));
     },
     disassociateRecords: function (parentId, parentType, relationshipName, childId, successCallback, errorCallback) {
      this._stringParameterCheck(parentId, "SDK.REST.disassociateRecords requires the parentId parameter is a string.");
      ///<param name="parentId" type="String">
      /// The Id of the record to be the parent record in the relationship
      /// </param>
      ///<param name="parentType" type="String">
      /// The Schema Name of the Entity type for the parent record.
      /// For an Account record, use "Account"
      /// </param>
      ///<param name="relationshipName" type="String">
      /// The Schema Name of the Entity Relationship to use to disassociate the records.
      /// To disassociate account records as a Parent account, use "Referencedaccount_parent_account"
      /// </param>
      ///<param name="childId" type="String">
      /// The Id of the record to be disassociated as the child record in the relationship
      /// </param>
      ///<param name="successCallback" type="Function">
      /// The function that will be passed through and be called by a successful response. 
      /// Nothing will be returned to this function.
      /// </param>
      ///<param name="errorCallback" type="Function">
      /// The function that will be passed through and be called by a failed response. 
      /// This function must accept an Error object as a parameter.
      /// </param>
      this._stringParameterCheck(parentType, "SDK.REST.disassociateRecords requires the parentType parameter is a string.");
      this._stringParameterCheck(relationshipName, "SDK.REST.disassociateRecords requires the relationshipName parameter is a string.");
      this._stringParameterCheck(childId, "SDK.REST.disassociateRecords requires the childId parameter is a string.");
      this._callbackParameterCheck(successCallback, "SDK.REST.disassociateRecords requires the successCallback parameter is a function.");
      this._callbackParameterCheck(errorCallback, "SDK.REST.disassociateRecords requires the errorCallback parameter is a function.");
    
      var req = new XMLHttpRequest();
      req.open("POST", encodeURI(this._ODataPath() + parentType + "Set(guid'" + parentId + "')/$links/" + relationshipName + "(guid'" + childId + "')"), true);
      req.setRequestHeader("Accept", "application/json");
      req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
      req.setRequestHeader("X-HTTP-Method", "DELETE");
      req.onreadystatechange = function () {
       if (this.readyState == 4 /* complete */) {
        req.onreadystatechange = null;
        if (this.status == 204 || this.status == 1223) {
         successCallback();
        }
        else {
         errorCallback(SDK.REST._errorHandler(this));
        }
       }
      };
      req.send();
     },
     __namespace: true
    };
    // </snippetSDKRESTJS>

    Content of the script wired up with OnSave:

    accountPresent = false;
    
    function onSaveValidation(context)
    {
    	var inputAccountCode = Xrm.Page.getAttribute("new_AccountCode").getValue();
    	var options = "$select=AccountId&$filter=new_AccountCode eq '" + inputAccountCode + "'";
    
    	SDK.REST.retrieveMultipleRecords("Account", options, retrieveAccountsCallBack, function (error) { alert(error.message); }, function(){});
    	if (accountPresent == true)
    	{
    		Xrm.Utility.alertDialog("Account Code already exists"); 
    		context.getEventArgs().preventDefault();
    	}
    }
    function retrieveAccountsCallBack(retrievedAccounts) 
    {
    	if (retrievedAccounts.length > 0)
    	{
    		accountPresent = true;
    	}
    }

    Make sure you check your Account Code field schema name. In addition, I added the json2.js to the Form library like I added the SDK.REST.js

    Try this out and let me know if it works!


    Admin QuikView Solution for CRM 2013

    Thursday, June 5, 2014 11:24 AM

All replies

  • Hi Shaun,

    Although the data in CRM is stored in SQL Server database, it's not needed to access the database itself to carry out any CRUD operation. In fact, Microsoft doesn't recommend that at all. There are other ways to access data - using the web service api's (SOAP and REST) exposed by CRM and if you are using Plugins, you will automatically get access to the CRM Service object which you can then use to call methods like Create, Update, RetreiveMultiple, etc.

    Now, to provide a solution to your problem. There are two ways of achieving the Account Code requirement:

    a. Use JavaScript : Using JavaScript, you can check "On Save" of an Account record if any other Account record exists (REST api in CRM allows you to fetch records using JavaScript by specifying a filter criteria) with the same Account Code and prevent save.

    b. Use Plugin : You can have a synchronous Plugin registered on Create/Update of Account entity and check if there is any other Account in CRM with the same Account Code and prevent it from saving by throwing an uncaught exception (generally the InvalidPluginExecution).

    Using the JavaScript makes it light weight since it's client side, but it will only be effective for Account record created from the CRM interface; records created in other ways (external apps, web services, other plugins, etc.) won't go through this validation. Using Plugins makes sure any record created in CRM by any means goes through the validation and is hence better.


    Admin QuikView Solution for CRM 2013

    Wednesday, June 4, 2014 4:25 AM
  • Hi

    Thanks for your reply, it's made this task seem so much easier than I thought,

    If I was to go down the javascript route as it is to my understanding that accounts are only created via CRM, I'm assuming there is some sort of code to specify the criteria and then possibly have to loop through them all saving them to an array, and then check the inputted account code against that array?

    If you have any links / sample code it would be greatly appreciated 

    Thanks, Shaun

    Wednesday, June 4, 2014 7:35 AM
  • Hi Shaun,

    First you might want to download the CRM 2013 SDK : http://www.microsoft.com/en-in/download/details.aspx?id=40321

    Once you have done that, open up the sample code in this location "\SDK\SampleCode\JS\RESTEndpoint\JavaScriptRESTRetrieveMultiple" to understand how the call is made to CRM.

    Secondly, download the CRM OData Query Designer (which helps you to form the query easily) : http://crm2011odatatool.codeplex.com/. Import it to CRM (its a managed solution, so it wont mess with other stuff).

    The query that you'd ideally be using would be something like the following:

    http://your_server_name:port_number/XRMServices/2011/OrganizationData.svc/$select=AccountId&$filter=AccountCode eq 'account_code_entered_on_form'

    This would give you any Account in CRM with that Account Code. You can simply check if there is a result (which means user is entering a duplicate Account Number), if not it's the first time. No need to iterate and check; becomes a lot easier.


    Admin QuikView Solution for CRM 2013

    Wednesday, June 4, 2014 7:49 AM
  • Hi again

    I have created the query as follows

    http://crmtest/siderise/xrmservices/2011/OrganizationData.svc/AccountSet?$select=new_AccountCode&$orderby=new_AccountCode asc&$filter=new_AccountCode ne null

    I was wondering what the ne null part at the end is as in your example code it had account code entered on the form.

    Also, where to go from here, do I use the code generator and paste that into the javascript section and go from there

    Or is it the actual generated query which I just mentioned above and put that in the javascript?

    Thanks, Shaun

    Wednesday, June 4, 2014 8:24 AM
  • Just found the answer to my first question about ne and eq

    Thanks, Shaun

    Wednesday, June 4, 2014 8:34 AM
  • Hi Shaun,

    To answer your second question, after you have formed the query and tested that it actually works (by clicking on Generate and Show Result in the CRM oData Query Designer); if you look into the SDK sample, you will find a section in the code that says

    var options = "$select=Name,Telephone1&$top=" + number;

    This is where you actually specify what to select, what would be the filter criteria and everything. This might help you understand : http://blogs.msdn.com/b/crm/archive/2011/03/02/using-odata-retrieve-in-microsoft-dynamics-crm-2011.aspx

    So, you need to modify the code as follows:

    var options = "$select=AccountId&$filter=AccountCode eq" + number_entered_in_the_form;

    The number entered in the form can be retrieved by using the code:

    Xrm.Page.getAttribute("account_code_field_name").getValue()


    Admin QuikView Solution for CRM 2013

    Wednesday, June 4, 2014 9:06 AM
  • Hi

    So I have done what you said any modified the var options and got the inputted value

    So to test would i do something along the lines of

    if (options == true)

    {

    alert("//input error messag");

    Else

    {

    //code for saving / leave blank?

    }

    Thanks, Shaun

    Wednesday, June 4, 2014 10:10 AM
  • Hi Shaun,

    Instead of wiring up the JavaScript on save, you could have it wired up on change of the Account Code field. So, in that case your code would be something like

    if(AccountCodeExistsInAnotherRecord)
    {
       alert("Account Code already exists");
       Xrm.Page.getAttribute("account_code_field").setValue("");
    }

    Make the Account Code mandatory and it would do your job.

    But in case you want to have it fire On Save, then the code would be

    function onSaveValidation(context)
    {
      if(AccountCodeAlreadyExists)
      {
         Xrm.Utility.alertDialog("Account Code already exists); 
         //or you could use simple alert
        context.getEventArgs().preventDefault();
      }
    }

    Make sure while registering the JavaScript OnSave you check the "Pass execution context as the first parameter" checkbox.  Visuals here : http://blog.sonomapartners.com/2013/12/creating-a-crm-2011-form-save-experience-in-crm-2013.html


    Admin QuikView Solution for CRM 2013

    Wednesday, June 4, 2014 10:38 AM
  • Hi 

    Thanks that give's me another option to use

     My code so far is as follows

    function RetieveAccountsCode()

    {

    var InputedAccountCode = Xrm.Page.getAttribute("new_accountcode").getValue();

    var Options = http://......... $select=new_AccountCode&$filter=new_AccountCode eq + InputedAccountCode

    Just struggling with where to go from here, what sort of if statement do i use?

    Thanks, Shaun

    Wednesday, June 4, 2014 10:51 AM
  • Hi Shaun,

    Try this:

    function onSaveValidation(context)
    {
    	var inputAccountCode = Xrm.Page.getAttribute("new_accountcode").getValue();
    	var options = "$select=new_AccountCode&$filter=new_AccountCode eq" + inputAccountCode;
    
    	SDK.REST.retrieveMultipleRecords("Account", options, retrieveAccountsCallBack, function (error) { alert(error.message); }, null);
    }
    function retrieveAccountsCallBack(retrievedAccounts) 
    {
    	if (retrievedAccounts.length > 0)
    	{
    		Xrm.Utility.alertDialog("Account Code already exists); 
    		context.getEventArgs().preventDefault();
    	}
    }

    Make sure you also link the sdk.rest.js to the form library.

    Admin QuikView Solution for CRM 2013



    Wednesday, June 4, 2014 12:04 PM
  • How to i go about linking the sdk.rest.js to the from library?

    Im currently working on the Account > Form Properties > Events

    The event library is where i've got the code

    Event handlers is set to form - onSave

    This may be where i'm having another problem is that I've got the function name set as

    'onSaveValidation(context)'

    Is this correct?

    Thanks, Shaun

     

    Thursday, June 5, 2014 7:58 AM
  • Hi Shaun,

    You've already added the webresource under your "Form Libraries" section right? So, add the sdk.rest.js also over there along with your existing webresource that contains the On Save function.

    For the OnSaveValidation() function, when you're linking it with Form OnSave make sure you have checked the "Pass execution context as the first parameter" checkbox as shown here :


    Admin QuikView Solution for CRM 2013

    Thursday, June 5, 2014 8:25 AM
  • Hi,

    Okay, so I've added the SDK. Rest. Js to the form library

    I've inputted my code, and inputted a onsave event called OnSaveValidation() which i've checked the checkbox as you stated

    I've recieved the following error

    Error: SDK.REST.retrieveMultipleRecords requires the OnComplete parameter is a function

    Completely lost now

    Thursday, June 5, 2014 8:58 AM
  • Hi,

    Can you replace this line

    SDK.REST.retrieveMultipleRecords("Account", options, retrieveAccountsCallBack, function (error) { alert(error.message); }, null);
    

    with this:

    SDK.REST.retrieveMultipleRecords("Account", options, retrieveAccountsCallBack, function (error) { alert(error.message); }, function(){});
    


    Admin QuikView Solution for CRM 2013

    Thursday, June 5, 2014 9:14 AM
  • Hi,

    Not sure if I'm making progress or not, but there's now no error messages, but CRM is still saving the record when the Account Code is the same as a previous one

    Any ideas? 

    Thanks, Shaun

    Thursday, June 5, 2014 9:43 AM
  • Hi,

    It appears the function is doing it asynchronously. Open up SDK.REST.js and locate this function:

    retrieveMultipleRecords: function (type, options, successCallback, errorCallback, OnComplete)

    Inside that you'll find a line :

    req.open("GET", this._ODataPath() + type + "Set" + optionsString, true);

    The last parameter, change it from true to false. So it should look like

    req.open("GET", this._ODataPath() + type + "Set" + optionsString, false);
    If you still don't get the result, you could try debugging and checking what might have went wrong.


    Admin QuikView Solution for CRM 2013

    Thursday, June 5, 2014 9:52 AM
  • Hi, new error has come up now

    Error : 400: Bad Request: Expression of type 'System.Boolean' expected at position 0

    Thanks, Shaun

    Thursday, June 5, 2014 9:59 AM
  • Hi Shaun,

    Let me check by actually using the code. Will update you shortly!


    Admin QuikView Solution for CRM 2013



    Thursday, June 5, 2014 10:40 AM
  • Hi Shaoun,

    Okay, updates here! I tried out the exact same thing and it works (just using a different field) and I'll provide the contents of all scripts used here.

    Content of SDK.REST.js

    // =====================================================================
    //  This file is part of the Microsoft Dynamics CRM SDK code samples.
    //
    //  Copyright (C) Microsoft Corporation.  All rights reserved.
    //
    //  This source code is intended only as a supplement to Microsoft
    //  Development Tools and/or on-line documentation.  See these other
    //  materials for detailed information regarding Microsoft code samples.
    //
    //  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
    //  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
    //  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
    //  PARTICULAR PURPOSE.
    // =====================================================================
    // <snippetSDKRESTJS>
    if (typeof (SDK) == "undefined")
    { SDK = { __namespace: true }; }
    SDK.REST = {
     _context: function () {
      ///<summary>
      /// Private function to the context object.
      ///</summary>
      ///<returns>Context</returns>
      if (typeof GetGlobalContext != "undefined")
      { return GetGlobalContext(); }
      else {
       if (typeof Xrm != "undefined") {
        return Xrm.Page.context;
       }
       else
       { throw new Error("Context is not available."); }
      }
     },
     _getClientUrl: function () {
      ///<summary>
      /// Private function to return the server URL from the context
      ///</summary>
      ///<returns>String</returns>
      var clientUrl = this._context().getClientUrl()
    
      return clientUrl;
     },
     _ODataPath: function () {
      ///<summary>
      /// Private function to return the path to the REST endpoint.
      ///</summary>
      ///<returns>String</returns>
      return this._getClientUrl() + "/XRMServices/2011/OrganizationData.svc/";
     },
     _errorHandler: function (req) {
      ///<summary>
      /// Private function return an Error object to the errorCallback
      ///</summary>
      ///<param name="req" type="XMLHttpRequest">
      /// The XMLHttpRequest response that returned an error.
      ///</param>
      ///<returns>Error</returns>
      //Error descriptions come from http://support.microsoft.com/kb/193625
      if (req.status == 12029)
      { return new Error("The attempt to connect to the server failed."); }
      if (req.status == 12007)
      { return new Error("The server name could not be resolved."); }
      var errorText;
      try
            { errorText = JSON.parse(req.responseText).error.message.value; }
      catch (e)
            { errorText = req.responseText }
    
      return new Error("Error : " +
            req.status + ": " +
            req.statusText + ": " + errorText);
     },
     _dateReviver: function (key, value) {
      ///<summary>
      /// Private function to convert matching string values to Date objects.
      ///</summary>
      ///<param name="key" type="String">
      /// The key used to identify the object property
      ///</param>
      ///<param name="value" type="String">
      /// The string value representing a date
      ///</param>
      var a;
      if (typeof value === 'string') {
       a = /Date\(([-+]?\d+)\)/.exec(value);
       if (a) {
        return new Date(parseInt(value.replace("/Date(", "").replace(")/", ""), 10));
       }
      }
      return value;
     },
     _parameterCheck: function (parameter, message) {
      ///<summary>
      /// Private function used to check whether required parameters are null or undefined
      ///</summary>
      ///<param name="parameter" type="Object">
      /// The parameter to check;
      ///</param>
      ///<param name="message" type="String">
      /// The error message text to include when the error is thrown.
      ///</param>
      if ((typeof parameter === "undefined") || parameter === null) {
       throw new Error(message);
      }
     },
     _stringParameterCheck: function (parameter, message) {
      ///<summary>
      /// Private function used to check whether required parameters are null or undefined
      ///</summary>
      ///<param name="parameter" type="String">
      /// The string parameter to check;
      ///</param>
      ///<param name="message" type="String">
      /// The error message text to include when the error is thrown.
      ///</param>
      if (typeof parameter != "string") {
       throw new Error(message);
      }
     },
     _callbackParameterCheck: function (callbackParameter, message) {
      ///<summary>
      /// Private function used to check whether required callback parameters are functions
      ///</summary>
      ///<param name="callbackParameter" type="Function">
      /// The callback parameter to check;
      ///</param>
      ///<param name="message" type="String">
      /// The error message text to include when the error is thrown.
      ///</param>
      if (typeof callbackParameter != "function") {
       throw new Error(message);
      }
     },
     createRecord: function (object, type, successCallback, errorCallback) {
      ///<summary>
      /// Sends an asynchronous request to create a new record.
      ///</summary>
      ///<param name="object" type="Object">
      /// A JavaScript object with properties corresponding to the Schema name of
      /// entity attributes that are valid for create operations.
      ///</param>
      ///<param name="type" type="String">
      /// The Schema Name of the Entity type record to create.
      /// For an Account record, use "Account"
      ///</param>
      ///<param name="successCallback" type="Function">
      /// The function that will be passed through and be called by a successful response. 
      /// This function can accept the returned record as a parameter.
      /// </param>
      ///<param name="errorCallback" type="Function">
      /// The function that will be passed through and be called by a failed response. 
      /// This function must accept an Error object as a parameter.
      /// </param>
      this._parameterCheck(object, "SDK.REST.createRecord requires the object parameter.");
      this._stringParameterCheck(type, "SDK.REST.createRecord requires the type parameter is a string.");
      this._callbackParameterCheck(successCallback, "SDK.REST.createRecord requires the successCallback is a function.");
      this._callbackParameterCheck(errorCallback, "SDK.REST.createRecord requires the errorCallback is a function.");
      var req = new XMLHttpRequest();
      req.open("POST", encodeURI(this._ODataPath() + type + "Set"), true);
      req.setRequestHeader("Accept", "application/json");
      req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
      req.onreadystatechange = function () {
       if (this.readyState == 4 /* complete */) {
        req.onreadystatechange = null;
        if (this.status == 201) {
         successCallback(JSON.parse(this.responseText, SDK.REST._dateReviver).d);
        }
        else {
         errorCallback(SDK.REST._errorHandler(this));
        }
       }
      };
      req.send(JSON.stringify(object));
     },
     retrieveRecord: function (id, type, select, expand, successCallback, errorCallback) {
      ///<summary>
      /// Sends an asynchronous request to retrieve a record.
      ///</summary>
      ///<param name="id" type="String">
      /// A String representing the GUID value for the record to retrieve.
      ///</param>
      ///<param name="type" type="String">
      /// The Schema Name of the Entity type record to retrieve.
      /// For an Account record, use "Account"
      ///</param>
      ///<param name="select" type="String">
      /// A String representing the $select OData System Query Option to control which
      /// attributes will be returned. This is a comma separated list of Attribute names that are valid for retrieve.
      /// If null all properties for the record will be returned
      ///</param>
      ///<param name="expand" type="String">
      /// A String representing the $expand OData System Query Option value to control which
      /// related records are also returned. This is a comma separated list of of up to 6 entity relationship names
      /// If null no expanded related records will be returned.
      ///</param>
      ///<param name="successCallback" type="Function">
      /// The function that will be passed through and be called by a successful response. 
      /// This function must accept the returned record as a parameter.
      /// </param>
      ///<param name="errorCallback" type="Function">
      /// The function that will be passed through and be called by a failed response. 
      /// This function must accept an Error object as a parameter.
      /// </param>
      this._stringParameterCheck(id, "SDK.REST.retrieveRecord requires the id parameter is a string.");
      this._stringParameterCheck(type, "SDK.REST.retrieveRecord requires the type parameter is a string.");
      if (select != null)
       this._stringParameterCheck(select, "SDK.REST.retrieveRecord requires the select parameter is a string.");
      if (expand != null)
       this._stringParameterCheck(expand, "SDK.REST.retrieveRecord requires the expand parameter is a string.");
      this._callbackParameterCheck(successCallback, "SDK.REST.retrieveRecord requires the successCallback parameter is a function.");
      this._callbackParameterCheck(errorCallback, "SDK.REST.retrieveRecord requires the errorCallback parameter is a function.");
    
      var systemQueryOptions = "";
    
      if (select != null || expand != null) {
       systemQueryOptions = "?";
       if (select != null) {
        var selectString = "$select=" + select;
        if (expand != null) {
         selectString = selectString + "," + expand;
        }
        systemQueryOptions = systemQueryOptions + selectString;
       }
       if (expand != null) {
        systemQueryOptions = systemQueryOptions + "&$expand=" + expand;
       }
      }
    
    
      var req = new XMLHttpRequest();
      req.open("GET", encodeURI(this._ODataPath() + type + "Set(guid'" + id + "')" + systemQueryOptions), true);
      req.setRequestHeader("Accept", "application/json");
      req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
      req.onreadystatechange = function () {
       if (this.readyState == 4 /* complete */) {
        req.onreadystatechange = null;
        if (this.status == 200) {
         successCallback(JSON.parse(this.responseText, SDK.REST._dateReviver).d);
        }
        else {
         errorCallback(SDK.REST._errorHandler(this));
        }
       }
      };
      req.send();
     },
     updateRecord: function (id, object, type, successCallback, errorCallback) {
      ///<summary>
      /// Sends an asynchronous request to update a record.
      ///</summary>
      ///<param name="id" type="String">
      /// A String representing the GUID value for the record to retrieve.
      ///</param>
      ///<param name="object" type="Object">
      /// A JavaScript object with properties corresponding to the Schema Names for
      /// entity attributes that are valid for update operations.
      ///</param>
      ///<param name="type" type="String">
      /// The Schema Name of the Entity type record to retrieve.
      /// For an Account record, use "Account"
      ///</param>
      ///<param name="successCallback" type="Function">
      /// The function that will be passed through and be called by a successful response. 
      /// Nothing will be returned to this function.
      /// </param>
      ///<param name="errorCallback" type="Function">
      /// The function that will be passed through and be called by a failed response. 
      /// This function must accept an Error object as a parameter.
      /// </param>
      this._stringParameterCheck(id, "SDK.REST.updateRecord requires the id parameter.");
      this._parameterCheck(object, "SDK.REST.updateRecord requires the object parameter.");
      this._stringParameterCheck(type, "SDK.REST.updateRecord requires the type parameter.");
      this._callbackParameterCheck(successCallback, "SDK.REST.updateRecord requires the successCallback is a function.");
      this._callbackParameterCheck(errorCallback, "SDK.REST.updateRecord requires the errorCallback is a function.");
      var req = new XMLHttpRequest();
    
      req.open("POST", encodeURI(this._ODataPath() + type + "Set(guid'" + id + "')"), true);
      req.setRequestHeader("Accept", "application/json");
      req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
      req.setRequestHeader("X-HTTP-Method", "MERGE");
      req.onreadystatechange = function () {
       if (this.readyState == 4 /* complete */) {
        req.onreadystatechange = null;
        if (this.status == 204 || this.status == 1223) {
         successCallback();
        }
        else {
         errorCallback(SDK.REST._errorHandler(this));
        }
       }
      };
      req.send(JSON.stringify(object));
     },
     deleteRecord: function (id, type, successCallback, errorCallback) {
      ///<summary>
      /// Sends an asynchronous request to delete a record.
      ///</summary>
      ///<param name="id" type="String">
      /// A String representing the GUID value for the record to delete.
      ///</param>
      ///<param name="type" type="String">
      /// The Schema Name of the Entity type record to delete.
      /// For an Account record, use "Account"
      ///</param>
      ///<param name="successCallback" type="Function">
      /// The function that will be passed through and be called by a successful response. 
      /// Nothing will be returned to this function.
      /// </param>
      ///<param name="errorCallback" type="Function">
      /// The function that will be passed through and be called by a failed response. 
      /// This function must accept an Error object as a parameter.
      /// </param>
      this._stringParameterCheck(id, "SDK.REST.deleteRecord requires the id parameter.");
      this._stringParameterCheck(type, "SDK.REST.deleteRecord requires the type parameter.");
      this._callbackParameterCheck(successCallback, "SDK.REST.deleteRecord requires the successCallback is a function.");
      this._callbackParameterCheck(errorCallback, "SDK.REST.deleteRecord requires the errorCallback is a function.");
      var req = new XMLHttpRequest();
      req.open("POST", encodeURI(this._ODataPath() + type + "Set(guid'" + id + "')"), true);
      req.setRequestHeader("Accept", "application/json");
      req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
      req.setRequestHeader("X-HTTP-Method", "DELETE");
      req.onreadystatechange = function () {
    
       if (this.readyState == 4 /* complete */) {
        req.onreadystatechange = null;
        if (this.status == 204 || this.status == 1223) {
         successCallback();
        }
        else {
         errorCallback(SDK.REST._errorHandler(this));
        }
       }
      };
      req.send();
    
     },
     retrieveMultipleRecords: function (type, options, successCallback, errorCallback, OnComplete) {
      ///<summary>
      /// Sends an asynchronous request to retrieve records.
      ///</summary>
      ///<param name="type" type="String">
      /// The Schema Name of the Entity type record to retrieve.
      /// For an Account record, use "Account"
      ///</param>
      ///<param name="options" type="String">
      /// A String representing the OData System Query Options to control the data returned
      ///</param>
      ///<param name="successCallback" type="Function">
      /// The function that will be passed through and be called for each page of records returned.
      /// Each page is 50 records. If you expect that more than one page of records will be returned,
      /// this function should loop through the results and push the records into an array outside of the function.
      /// Use the OnComplete event handler to know when all the records have been processed.
      /// </param>
      ///<param name="errorCallback" type="Function">
      /// The function that will be passed through and be called by a failed response. 
      /// This function must accept an Error object as a parameter.
      /// </param>
      ///<param name="OnComplete" type="Function">
      /// The function that will be called when all the requested records have been returned.
      /// No parameters are passed to this function.
      /// </param>
      this._stringParameterCheck(type, "SDK.REST.retrieveMultipleRecords requires the type parameter is a string.");
      if (options != null)
       this._stringParameterCheck(options, "SDK.REST.retrieveMultipleRecords requires the options parameter is a string.");
      this._callbackParameterCheck(successCallback, "SDK.REST.retrieveMultipleRecords requires the successCallback parameter is a function.");
      this._callbackParameterCheck(errorCallback, "SDK.REST.retrieveMultipleRecords requires the errorCallback parameter is a function.");
      this._callbackParameterCheck(OnComplete, "SDK.REST.retrieveMultipleRecords requires the OnComplete parameter is a function.");
    
      var optionsString;
      if (options != null) {
       if (options.charAt(0) != "?") {
        optionsString = "?" + options;
       }
       else
       { optionsString = options; }
      }
      var req = new XMLHttpRequest();
      req.open("GET", this._ODataPath() + type + "Set" + optionsString, false);
      req.setRequestHeader("Accept", "application/json");
      req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
      req.onreadystatechange = function () {
       if (this.readyState == 4 /* complete */) {
        req.onreadystatechange = null;
        if (this.status == 200) {
         var returned = JSON.parse(this.responseText, SDK.REST._dateReviver).d;
         successCallback(returned.results);
         if (returned.__next != null) {
          var queryOptions = returned.__next.substring((SDK.REST._ODataPath() + type + "Set").length);
          SDK.REST.retrieveMultipleRecords(type, queryOptions, successCallback, errorCallback, OnComplete);
         }
         else
         { OnComplete(); }
        }
        else {
         errorCallback(SDK.REST._errorHandler(this));
        }
       }
      };
      req.send();
     },
     associateRecords: function (parentId, parentType, relationshipName, childId, childType, successCallback, errorCallback) {
      this._stringParameterCheck(parentId, "SDK.REST.associateRecords requires the parentId parameter is a string.");
      ///<param name="parentId" type="String">
      /// The Id of the record to be the parent record in the relationship
      /// </param>
      ///<param name="parentType" type="String">
      /// The Schema Name of the Entity type for the parent record.
      /// For an Account record, use "Account"
      /// </param>
      ///<param name="relationshipName" type="String">
      /// The Schema Name of the Entity Relationship to use to associate the records.
      /// To associate account records as a Parent account, use "Referencedaccount_parent_account"
      /// </param>
      ///<param name="childId" type="String">
      /// The Id of the record to be the child record in the relationship
      /// </param>
      ///<param name="childType" type="String">
      /// The Schema Name of the Entity type for the child record.
      /// For an Account record, use "Account"
      /// </param>
      ///<param name="successCallback" type="Function">
      /// The function that will be passed through and be called by a successful response. 
      /// Nothing will be returned to this function.
      /// </param>
      ///<param name="errorCallback" type="Function">
      /// The function that will be passed through and be called by a failed response. 
      /// This function must accept an Error object as a parameter.
      /// </param>
      this._stringParameterCheck(parentType, "SDK.REST.associateRecords requires the parentType parameter is a string.");
      this._stringParameterCheck(relationshipName, "SDK.REST.associateRecords requires the relationshipName parameter is a string.");
      this._stringParameterCheck(childId, "SDK.REST.associateRecords requires the childId parameter is a string.");
      this._stringParameterCheck(childType, "SDK.REST.associateRecords requires the childType parameter is a string.");
      this._callbackParameterCheck(successCallback, "SDK.REST.associateRecords requires the successCallback parameter is a function.");
      this._callbackParameterCheck(errorCallback, "SDK.REST.associateRecords requires the errorCallback parameter is a function.");
    
      var req = new XMLHttpRequest();
      req.open("POST", encodeURI(this._ODataPath() + parentType + "Set(guid'" + parentId + "')/$links/" + relationshipName), true);
      req.setRequestHeader("Accept", "application/json");
      req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
      req.onreadystatechange = function () {
       if (this.readyState == 4 /* complete */) {
        req.onreadystatechange = null;
        if (this.status == 204 || this.status == 1223) {
         successCallback();
        }
        else {
         errorCallback(SDK.REST._errorHandler(this));
        }
       }
      };
      var childEntityReference = {}
      childEntityReference.uri = this._ODataPath() + "/" + childType + "Set(guid'" + childId + "')";
      req.send(JSON.stringify(childEntityReference));
     },
     disassociateRecords: function (parentId, parentType, relationshipName, childId, successCallback, errorCallback) {
      this._stringParameterCheck(parentId, "SDK.REST.disassociateRecords requires the parentId parameter is a string.");
      ///<param name="parentId" type="String">
      /// The Id of the record to be the parent record in the relationship
      /// </param>
      ///<param name="parentType" type="String">
      /// The Schema Name of the Entity type for the parent record.
      /// For an Account record, use "Account"
      /// </param>
      ///<param name="relationshipName" type="String">
      /// The Schema Name of the Entity Relationship to use to disassociate the records.
      /// To disassociate account records as a Parent account, use "Referencedaccount_parent_account"
      /// </param>
      ///<param name="childId" type="String">
      /// The Id of the record to be disassociated as the child record in the relationship
      /// </param>
      ///<param name="successCallback" type="Function">
      /// The function that will be passed through and be called by a successful response. 
      /// Nothing will be returned to this function.
      /// </param>
      ///<param name="errorCallback" type="Function">
      /// The function that will be passed through and be called by a failed response. 
      /// This function must accept an Error object as a parameter.
      /// </param>
      this._stringParameterCheck(parentType, "SDK.REST.disassociateRecords requires the parentType parameter is a string.");
      this._stringParameterCheck(relationshipName, "SDK.REST.disassociateRecords requires the relationshipName parameter is a string.");
      this._stringParameterCheck(childId, "SDK.REST.disassociateRecords requires the childId parameter is a string.");
      this._callbackParameterCheck(successCallback, "SDK.REST.disassociateRecords requires the successCallback parameter is a function.");
      this._callbackParameterCheck(errorCallback, "SDK.REST.disassociateRecords requires the errorCallback parameter is a function.");
    
      var req = new XMLHttpRequest();
      req.open("POST", encodeURI(this._ODataPath() + parentType + "Set(guid'" + parentId + "')/$links/" + relationshipName + "(guid'" + childId + "')"), true);
      req.setRequestHeader("Accept", "application/json");
      req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
      req.setRequestHeader("X-HTTP-Method", "DELETE");
      req.onreadystatechange = function () {
       if (this.readyState == 4 /* complete */) {
        req.onreadystatechange = null;
        if (this.status == 204 || this.status == 1223) {
         successCallback();
        }
        else {
         errorCallback(SDK.REST._errorHandler(this));
        }
       }
      };
      req.send();
     },
     __namespace: true
    };
    // </snippetSDKRESTJS>

    Content of the script wired up with OnSave:

    accountPresent = false;
    
    function onSaveValidation(context)
    {
    	var inputAccountCode = Xrm.Page.getAttribute("new_AccountCode").getValue();
    	var options = "$select=AccountId&$filter=new_AccountCode eq '" + inputAccountCode + "'";
    
    	SDK.REST.retrieveMultipleRecords("Account", options, retrieveAccountsCallBack, function (error) { alert(error.message); }, function(){});
    	if (accountPresent == true)
    	{
    		Xrm.Utility.alertDialog("Account Code already exists"); 
    		context.getEventArgs().preventDefault();
    	}
    }
    function retrieveAccountsCallBack(retrievedAccounts) 
    {
    	if (retrievedAccounts.length > 0)
    	{
    		accountPresent = true;
    	}
    }

    Make sure you check your Account Code field schema name. In addition, I added the json2.js to the Form library like I added the SDK.REST.js

    Try this out and let me know if it works!


    Admin QuikView Solution for CRM 2013

    Thursday, June 5, 2014 11:24 AM
  • Worked a treat!

    Thanks for all your help! hopefully others dealing with this issue will be able to solve it now

    Thanks, Shaun

    Thursday, June 5, 2014 11:43 AM
  • Glad I was able to help! :)

    Admin QuikView Solution for CRM 2013

    Thursday, June 5, 2014 11:45 AM