Answered by:
How to stop a field having the same data stored in multiple records

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.
- Marked as answer by Shaun Harrison Wednesday, June 4, 2014 7:35 AM
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.
- Marked as answer by Shaun Harrison Wednesday, June 4, 2014 7:57 AM
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()
- Marked as answer by Shaun Harrison Wednesday, June 4, 2014 10:10 AM
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
- Edited by Anupam Bishui Wednesday, June 4, 2014 12:06 PM
- Marked as answer by Shaun Harrison Thursday, June 5, 2014 7:36 AM
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 :
- Marked as answer by Shaun Harrison Thursday, June 5, 2014 8:59 AM
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!
- Marked as answer by Shaun Harrison Thursday, June 5, 2014 11:43 AM
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.
- Marked as answer by Shaun Harrison Wednesday, June 4, 2014 7:35 AM
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.
- Marked as answer by Shaun Harrison Wednesday, June 4, 2014 7:57 AM
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()
- Marked as answer by Shaun Harrison Wednesday, June 4, 2014 10:10 AM
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.htmlWednesday, 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
- Edited by Anupam Bishui Wednesday, June 4, 2014 12:06 PM
- Marked as answer by Shaun Harrison Thursday, June 5, 2014 7:36 AM
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 :
- Marked as answer by Shaun Harrison Thursday, June 5, 2014 8:59 AM
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
- Marked as answer by Shaun Harrison Thursday, June 5, 2014 8:59 AM
- Unmarked as answer by Shaun Harrison Thursday, June 5, 2014 8:59 AM
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(){});
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.
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
- Edited by Anupam Bishui Thursday, June 5, 2014 11:23 AM
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!
- Marked as answer by Shaun Harrison Thursday, June 5, 2014 11:43 AM
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! :)Thursday, June 5, 2014 11:45 AM