Dynamics CRM 2011中跨实体子网格复制特殊服务数据技术问询
Alright, let's work through how to sync those Special Services records from an Opportunity to its related Quotes and Orders in Dynamics CRM 2011. This is a common scenario, and there are two solid approaches depending on whether you need server-side enforceability or just client-side form-based sync.
1. Plugin-Based Synchronization (Recommended for Full Data Consistency)
This server-side method ensures Special Services get synced no matter how the Quote/Order is created—whether via the form, API, or bulk import. It’s the most reliable way to keep data aligned across entities.
Step-by-Step Setup
First, Create Required Relationships
You’ll need to establish 1:N relationships between:- Quote (primary) and Special Services (related)
- Order (primary) and Special Services (related)
Make sure to set up clear navigation properties so the subgrids on Quote/Order forms can display the synced records.
Write the Plugin Logic
The plugin will trigger when a Quote/Order is created or when its linked Opportunity is updated. Here’s a simplified C# example tailored to CRM 2011:using Microsoft.Xrm.Sdk; using System.Linq; public class SyncSpecialServices : IPlugin { public void Execute(IServiceProvider serviceProvider) { var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); var service = serviceFactory.CreateOrganizationService(context.UserId); // Target is the Quote or Order being created/updated if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity target) { EntityReference oppRef = null; string targetEntity = target.LogicalName; // Grab the linked Opportunity ID from the target record if (targetEntity == "quote") { oppRef = target.GetAttributeValue<EntityReference>("opportunityid"); } else if (targetEntity == "salesorder") // CRM 2011 uses "salesorder" as Order's logical name { oppRef = target.GetAttributeValue<EntityReference>("opportunityid"); } if (oppRef != null) { // Fetch all Special Services tied to the Opportunity var ssQuery = new QueryExpression("new_services") // Replace with your actual Special Services logical name { ColumnSet = new ColumnSet(true), // Adjust to only fetch needed columns for better performance Criteria = new FilterExpression { Conditions = { new ConditionExpression("opportunityid", ConditionOperator.Equal, oppRef.Id) } } }; var specialServices = service.RetrieveMultiple(ssQuery).Entities; foreach (var ss in specialServices) { // Create a new Special Service linked to the Quote/Order var newSS = new Entity("new_services"); // Copy all fields except the original Opportunity reference foreach (var attr in ss.Attributes) { if (attr.Key != "opportunityid") { newSS[attr.Key] = attr.Value; } } // Link to the current Quote/Order if (targetEntity == "quote") { newSS["quoteid"] = new EntityReference("quote", target.Id); } else if (targetEntity == "salesorder") { newSS["salesorderid"] = new EntityReference("salesorder", target.Id); } service.Create(newSS); } } } } }Register the Plugin
Use the CRM 2011 Plugin Registration Tool to register this plugin:- For Quotes: Register on the
CreateandUpdatemessages (filter the Update message to only trigger when theopportunityidfield changes) - For Orders: Repeat the same registration steps
Set the execution stage to Post-Operation to ensure the Quote/Order is already linked to an Opportunity before syncing.
- For Quotes: Register on the
2. JavaScript-Based Solution (Form-Only Sync)
If you only need to sync Special Services when users create/edit Quotes/Orders via the CRM form, this client-side approach works well. It’s simpler to set up but won’t catch syncs from non-form actions.
Step-by-Step Setup
Add Subgrids to Quote/Order Forms
Make sure your Quote and Order forms have a subgrid configured to display Special Services records linked to the current record.Write the Sync Script
This script will pull Special Services from the linked Opportunity and create copies tied to the current Quote/Order. Add it as a web resource to your CRM:function syncSpecialServicesToRecord() { var oppLookup = Xrm.Page.getAttribute("opportunityid").getValue(); if (!oppLookup) return; var oppId = oppLookup[0].id.replace("{", "").replace("}", ""); var currentRecordId = Xrm.Page.data.entity.getId().replace("{", "").replace("}", ""); var currentEntity = Xrm.Page.data.entity.getEntityName(); // Fetch Special Services from Opportunity via OData var req = new XMLHttpRequest(); req.open("GET", Xrm.Page.context.getServerUrl() + "/XRMServices/2011/OrganizationData.svc/new_servicesSet?$filter=opportunityid/Id eq guid'" + oppId + "'", true); req.setRequestHeader("Accept", "application/json"); req.setRequestHeader("Content-Type", "application/json; charset=utf-8"); req.onreadystatechange = function () { if (this.readyState === 4) { req.onreadystatechange = null; if (this.status === 200) { var results = JSON.parse(this.responseText).d.results; for (var i = 0; i < results.length; i++) { var ss = results[i]; // Build new Special Service record var newSS = { "new_name": ss.new_name, // Replace with your actual field names "new_description": ss.new_description, "__metadata": { "type": "Microsoft.Crm.Sdk.Data.Services.new_services" } }; // Link to current Quote/Order if (currentEntity === "quote") { newSS.quoteid = { "__metadata": { "type": "Microsoft.Crm.Sdk.Data.Services.EntityReference" }, "Id": currentRecordId, "LogicalName": "quote" }; } else if (currentEntity === "salesorder") { newSS.salesorderid = { "__metadata": { "type": "Microsoft.Crm.Sdk.Data.Services.EntityReference" }, "Id": currentRecordId, "LogicalName": "salesorder" }; } // Create the new record via OData var createReq = new XMLHttpRequest(); createReq.open("POST", Xrm.Page.context.getServerUrl() + "/XRMServices/2011/OrganizationData.svc/new_servicesSet", true); createReq.setRequestHeader("Accept", "application/json"); createReq.setRequestHeader("Content-Type", "application/json; charset=utf-8"); createReq.send(JSON.stringify(newSS)); } // Refresh the subgrid to show new records Xrm.Page.getControl("subgrid_services").refresh(); } else { alert("Error syncing Special Services: " + this.statusText); } } }; req.send(); }Attach the Script to Form Events
- For Quotes: Bind the
syncSpecialServicesToRecordfunction to theopportunityidfield’sOnChangeevent and the form’sOnLoadevent (if Quotes are created directly from Opportunities) - For Orders: Repeat the same event bindings
- For Quotes: Bind the
Key Things to Keep in Mind
- Logical Names Matter: Replace all placeholder logical names (like
new_services) with the actual names from your CRM system. You can find these in the entity customization settings. - Avoid Duplicates: Add logic to check if a Special Service already exists for the Quote/Order before creating a new one (e.g., compare a unique identifier field or match on name + description).
- Performance: If you have hundreds of Special Services per Opportunity, optimize queries to fetch only necessary fields and consider batch processing in plugins to avoid timeouts.
- Security: Ensure the plugin’s execution user (or the form user for JavaScript) has create permissions for Special Services records.
内容的提问来源于stack exchange,提问作者Alireza

