在本章中,我们将介绍以下内容:
- 服务器端并发控制
- 客户端并发控制
- 在事务中执行请求
- 批处理请求
- 暂存数据导入
- 创建早期绑定的实体类
- 扩展CrmSvcUtil用Filter
- 扩展CrmSvcUtil以生成选项集枚举
- 使用CRM配置迁移工具跨实例迁移配置
简介
回顾Dynamics CRM的历史,该产品经过多年的发展以适应最新的趋势。Dynamics CRM的前几个版本旨在进行内部部署和内部使用。后来的版本引入了面向互联网的部署和微软云SaaS产品。
随着正在进行的发布引入了新的企业功能以满足大型实施的需要,还引入了额外的功能和工具来增强Dynamics企业的实力。
本章将介绍一些在实现企业级解决方案时有用的SDK精华。我们将涵盖服务器端和自定义客户端乐观并发控制、事务和批处理请求、使用新数据加载器服务的大数据导入、实例之间的配置数据传输,以及CrmSvcUtil扩展。
服务器端并发控制
乐观并发控制是Dynamics365企业级用户的首要要求之一。当多个用户有可能同时更新记录时,并发控制就变得很重要。毕竟,并发使用是组织放弃在电子表格中跟踪工作的主要原因之一。
乐观并发控制是当用户尝试更新自上次加载以来已经更新的记录时,检测并发更改的机制。这与悲观并发控制相反,悲观并发控制在读取记录时锁定记录,以阻止其他用户访问它们。锁定有时会导致死锁。
在这个方法中,我们将演示如何在更新记录时启用乐观并发控制。
准备
为了测试此代码,您需要对Dynamics 365的帐户实体、带有.NET 4.5.2的Visual Studio IDE和Dynamics 365 NuGet包进行读/写访问,才能访问SDK库。被测试的实体必须启用其乐观并发控制。为了检查您的实体(本例中的帐户)是否启用了乐观并发控制,请导航到以下URL来检查其元数据:
<OrganizationUrl>/api/data/v9.2/EntityDefinitions?$filter=SchemaName eq 'Account'&$select=IsOptimisticConcurrencyEnabled
不要忘记将<OrganizationUrl>替换为实例的URL。
如何实施
- 在Visual Studio中创建一个控制台应用程序名为Packt.Xrm.ConcurrencyControl.
- 右键单击“引用”,单击“管理NuGet包”,然后搜索并安装Microsoft.CrmSdk.XrmTooling.CoreAssembly
此屏幕截图类似: - 在.cs文件中包含以下using语句:
using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Messages; using Microsoft.Xrm.Sdk.Query; using Microsoft.Xrm.Tooling.Connector;
- 将以下代码复制并插入到主函数中(不要忘记更新连接字符串):
var connstr = "AuthType=OAuth;Username=xxx.onmicrosoft.com;Password=xxx;Url=https://xxx.crm5.dynamics.com;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=http://localhost;TokenCacheStorePath=C:\\CRMOnlinePRJ\\ConsoleApp7\\ConsoleApp7\\TextFile1.txt;LoginPrompt=Auto"; var crmSvc = new CrmServiceClient(connstr); var serviceProxy = crmSvc.OrganizationWebProxyClient != null ? (IOrganizationService)crmSvc.OrganizationWebProxyClient : (IOrganizationService)crmSvc.OrganizationServiceProxy; var accountToCreate = new Entity("account"); accountToCreate["name"] = "Packt V1.0"; //Create an account var accountGuid = serviceProxy.Create(accountToCreate); // Retrieve the account var account = serviceProxy.Retrieve("account", accountGuid, new ColumnSet("name")); account["name"] = "Packt v2.0"; // Built the request UpdateRequest accountUpdate = new UpdateRequest() { Target = account, ConcurrencyBehavior = ConcurrencyBehavior.IfRowVersionMatches }; // Update the account UpdateResponse accountUpdateResponse1 = (UpdateResponse)serviceProxy.Execute(accountUpdate); serviceProxy.Delete("account", accountGuid);
它是如何工作的
在步骤1和步骤2中,我们首先添加了必要的NuGet包,以确保引用正确的库。
然后,我们引用了名称空间,特别是Microsoft.Xrm.SDK,它提供了组织服务类,允许我们执行必要的创建、读取、更新和删除CRUD操作。
Microsoft.Xrm.Tooling.Connector命名空间中的CrmServiceClient类提供了连接到Dynamics 365的首选方式。这种连接方法是最佳实践,因为它是经得起未来考验的。使用端点URL直接连接到web服务并不是一种好的做法,因为端点可能会随着时间的推移而更改。Xrm.Tooling连接负责处理幕后的管道。
在代码中,我们首先使用Office365凭据构建连接字符串。
请确保更改连接字符串中的值以反映您的凭据和组织。有关连接字符串的更多详细信息,请访问
Use connection strings in XRM tooling to connect to Dynamics 365 | Microsoft Learn
创建组织服务后,我们首先创建一个返回帐户GUID的帐户。然后,我们检索该帐户并尝试保存两次,使用ConcurrencyBehavior设置为ConcurrencyBehavior.IfRowVersionMatches的更新请求。
这样可以确保如果版本号不匹配,则会引发异常。假设我们没有在更新之间重新加载记录,版本号保持不变,第二次保存会引发异常。
在幕后,SDK正在利用底层SQLtables中的VersionNumber列来检查版本是否与提交的版本匹配。如果您有本地Dynamics365实例,则在引发异常之前,SQLprofiler会话将显示以下SQLquery:
exec sp_executesql N'select
convert(bigint, "account0".VersionNumber) as "versionnumber"
from AccountBase as "account0" (UPDLOCK)
where ("account0".AccountId = @AccountId0)',
N'@AccountId0 uniqueidentifier',
@AccountId0='DCEAF3CF-C1B3-E611-9428-0050569228E8'
还有更多。。。
为了避免此异常,您必须在运行第三次更新之前重新加载记录。在第一次更新后插入以下代码,以便在提交第三次更新之前使用最新版本再次检索记录:
account = serviceProxy.Retrieve("account", accountGuid, new ColumnSet("name"));
accountUpdate.Target = account;
客户端并发控制
如前一个方法中所述,在生成服务器端UpdateRequest或DeleteRequest时可以启用服务器端并发控制,但是,在使用前端UI时,客户端并发当前不可用。
在这个方法中,我们将创建一个自定义JavaScript库,在保存记录时检查并发性,本质上启用了客户端乐观并发控制机制。然后,我们将为正在使用的联系人实体启用它。
准备
为了构建此自定义项,您需要系统自定义程序或更高级别的安全角色。但是,最终用户只需要对实体进行读取/创建/更新访问。
如何实施
- 导航到“设置”|“解决方案”|“Packt”。
- 单击Web资源并单击新建,输入packt_/js/concurrenty.js作为名称,从类型中选择JScript,然后单击文本编辑器。
- 复制并插入以下代码:
var packtNs = packtNs || {}; packtNs.concurrency = packtNs.concurrency || {}; var _recordLoadedVersion; var _schemaName = ""; var _saving = false; packtNs.concurrency.init = function (schemaName) { _schemaName = schemaName; var modifiedResult = packtNs.concurrency.checkLastModified("0"); _recordLoadedVersion = modifiedResult.modifiedVersion; Xrm.Page.ui.clearFormNotification("concurrency"); Xrm.Page.data.entity.removeOnSave(packtNs.concurrency.checkConcurrency); Xrm.Page.data.entity.addOnSave(packtNs.concurrency.checkConcurrency); } packtNs.concurrency.checkConcurrency = function (executionObj) { if(_saving){ return; } var saveMode = executionObj.getEventArgs().getSaveMode(); executionObj.getEventArgs().preventDefault(); var modifiedObject = packtNs.concurrency.checkLastModified(_recordLoadedVersion); if (!modifiedObject.hasChanged) { packtNs.concurrency.callSecondSave(executionObj, saveMode); return; } if (saveMode === 70) { Xrm.Page.ui.setFormNotification("Seems like this record has been updated by " + modifiedObject.modifiedBy + ". AutoSave has been aborted.", "WARNING", "concurrency"); } else if (saveMode === 59 || saveMode === 2 || saveMode === 1) { Xrm.Utility.confirmDialog("Seems like this record has been updated by " + modifiedObject.modifiedBy + ".\nAre you sure you still want to save it?", function () { packtNs.concurrency.callSecondSave(executionObj,saveMode); }, null); } } packtNs.concurrency.callSecondSave = function (executionObj, saveType){ //Omitted code. Call the correct save method. } packtNs.concurrency.checkLastModified = function (recordLoadedVersion) { //Omitted code. Compare the result from JSON @odata.etag with recordedLoadedVersion }
- 返回Dynamics 365中的数据包解决方案,导航到实体|联系人|窗体,然后双击主窗体。
- 单击“表单属性”,然后在“表单库”下,单击+“添加”
- 从列表中选择在步骤2中创建的JavaScript库(您可以使用搜索功能来过滤值),然后单击Add,然后单击OK。
- 在事件处理程序下,验证Control是否设置为Form,Event是否设置为OnLoad,然后单击+Add。
- 确保在Library下拉列表中选择了packt_/js/concurrenty.js,然后在Function字段中输入packtNs.concurrent.init。
- 在参数字段中输入contacts。
- 单击“保存并关闭”,然后在解决方案窗口中单击“发布所有自定义设置”。
它是如何工作的
在这个方法的第3步中,我们创建了一个函数,用于检查记录自打开以来是否在后台进行了更新。initJavaScript函数连接到表单的OnLoad事件,后者又连接OnSave事件以调用checkConcurrency函数(步骤7和步骤8)。
checkConcurrency函数有两种行为。如果正在进行保存,则该保存已存在。如果不是,它将通过调用checkLastModified函数来检查记录自首次加载以来是否发生了更改,该函数反过来调用Web API来检索记录的服务器版本,并将其与当前版本进行比较。
每次保存时触发的恒定版本检查检索将对Dynamics 365实例的性能产生影响。
如果未检测到任何更改,则默认保存将被取消,并替换为具有回调方法的保存,以便在保存完成后检索最新版本。
假设保存事件被替换为另一个事件,则必须格外注意任何附加的有线OnSave函数。请确保顺序已正确注册,并且当调用save两次时,方法不会调用两次。
如果检测到并发更新,根据事件的不同,会向用户显示以下通知或确认消息,以检查他们是否真的要保存:
当触发自动保存时,一个不引人注目的通知也会显示在记录的顶部:
请注意,这只会在使用启用了JavaScript的特定表单时启用并发控制。其他方式,如从可编辑网格更新记录或批量编辑,不会触发控制例程。
已知限制
此自定义功能最适用于Internet Explorer(Chrome在保存和关闭事件方面存在问题)。该脚本也适用于在线实例。如果您想将其更改为内部部署的Dynamics365实例,则必须确保checkLastModified下的Web API URLconstruction相应更新,并包括您的组织名称。
该脚本也很简洁,仅适用于Save、Save and Close和Auto Save调用。为了使其适应其他保存方法,您必须添加额外的保存模式。
此自定义的另一个限制是,它仅适用于更新的实体——新Dynamics 365实例中的大多数实体(Write code for Microsoft Dynamics 365 forms | Microsoft Learn)——因为它使用Xrm.Page.data.save()函数的回调功能。
考虑到限制和性能影响的列表,本方法中的代码更多地关注于了解潜在的Dynamics 365客户端API功能,而不是生产使用。
在事务中执行请求
当Dynamics365使用到其全部企业容量时,通常一个复合操作将跨越多个CRUD操作,并且需要以原子方式执行。这些要求通常规定,如果任何操作失败,整个过程必须回滚以确保数据完整性。
自Dynamics CRM 2015起,SDK库中引入了ExecuteTransactionRequest,允许在单个数据库级事务中执行自定义代码。
在这个方法中,我们将在单个交易的范围内创建一个帐户和一个联系人。
准备
为了测试此代码,您需要对account和contact实体、Visual Studio IDE以及Dynamics 365 NuGet包进行写访问,才能访问SDK库(有关如何设置Visual Studio解决方案的详细信息,请参阅本章中的第一个方法)。
如何实施
- 在Visual Studio中创建一个名为Packt.Xrm.Transaction的新控制台应用程序。
- 右键单击“引用”,然后单击“管理NuGet包”,然后搜索并安装Microsoft.CrmSdk.XrmTooling.CoreAssembly。
- 在.cs文件中包含以下using语句:
using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Messages; using Microsoft.Xrm.Tooling.Connector;
- 在主函数中复制并插入以下代码(不要忘记更新连接字符串):
var connectionString = "AuthType=Office365;Username=@.onmicrosoft.com;Password=; Url=https://.crm6.dynamics.com"; var crmSvc = new CrmServiceClient(connectionString); using (var serviceProxy = crmSvc.OrganizationServiceProxy) { //Create request collection var request = new OrganizationRequestCollection() { new CreateRequest { Target = new Entity("account") { ["name"] = "Packt Account" } }, new CreateRequest { Target = new Entity("contact") { ["firstname"] = "Packt", ["lastname"] = "Contact" } } }; //Create Transaction and pass previously created request collection var requestToCreateRecords = new ExecuteTransactionRequest() { // Create an empty organization request collection. Requests = request, ReturnResponses = true }; //Execute requests within the transaction var responseForCreateRecords = (ExecuteTransactionResponse)serviceProxy.Execute(requestToCreateRecords); // Display the results of each response. foreach (var responseItem in responseForCreateRecords.Responses) { Console.WriteLine("Created record with GUID {0}", responseItem.Results["id"].ToString()); } }
它是如何工作的
与第一个方法类似,我们首先创建了一个新的解决方案,并引用了正确的NuGet包。然后,我们在步骤3中添加了正确的库引用。
在步骤4中,我们首先通过从Microsoft向CrmServiceClient类注入一个简单的连接字符串来连接Dynamics365。来自Microsoft.Xrm.Tooling命名空间。
然后,我们创建了一个请求集合:创建帐户请求和创建联系人记录请求。然后,我们将请求集合传递给ExecuteTransactionRequest类。ExecuteTransactionRequest提供用于执行请求列表的事务。
如果任何一个请求失败,则所有其他请求都会回滚。在我们的示例中,如果联系人创建请求失败,则帐户创建请求将回滚。
考虑到我们已经在ExecuteTransactionRequest中指示通过将ReturnResponses设置为True来返回响应,我们现在可以遍历每个响应并显示创建的每个记录的GUID。然后,我们可以将每个响应与请求关联起来,因为它们以与请求相同的顺序返回。
还有更多。。。
要了解有关ExecuteTransactionRequest类的更多信息,请访问ExecuteTransactionRequest Class (Microsoft.Xrm.Sdk.Messages) | Microsoft Learn
批处理请求
通常在企业应用程序中,需要批量处理大量数据以提高性能。在Microsoft SQL(以及许多其他数据库引擎)的发展过程中观察到了批数据处理模式,其中引入了批SQL执行以提高效率。
在这个方法中,我们将使用上一个方法中的相同请求集合来创建一个帐户和一个联系人;但是,执行将在批处理请求中完成,而不是在事务请求中。
准备
为了测试此代码,您需要对帐户和联系人实体、Visual Studio IDE以及Dynamics 365 NuGet包进行写访问,才能访问SDK库(有关如何设置Visual Studio解决方案的详细信息,请参阅本章中的第一个方法)。
如何实施
- 在Visual Studio中创建一个名为Packt.Xrm.Batch的新控制台应用程序。
- 右键单击“引用”,然后单击“管理NuGet包”,然后搜索并安装Microsoft.CrmSdk.XrmTooling.CoreAssembly。
- 在.cs文件中包含以下using语句:
using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Messages; using Microsoft.Xrm.Tooling.Connector;
- 在主函数中复制并插入以下代码(不要忘记更新连接字符串):
var connectionString = "AuthType=Office365;Username=@.onmicrosoft.com;Password=;Url=https://.crm6.dynamics.com"; var crmSvc = new CrmServiceClient(connectionString); using (var serviceProxy = crmSvc.OrganizationServiceProxy) { //Create request collection var request = new OrganizationRequestCollection() { new CreateRequest { Target = new Entity("account") { ["name"] = "Packt Account" } }, new CreateRequest { Target = new Entity("contact") { ["firstname"] = "Packt", ["lastname"] = "Contact" } } }; //Create Transaction and pass previously created request collection var requestToCreateRecords = new ExecuteMultipleRequest() { // Create an empty organization request collection. Requests = request, Settings = new ExecuteMultipleSettings() { ContinueOnError = true, ReturnResponses = true } }; //Execute requests within the transaction var responseForCreateRecords = (ExecuteMultipleResponse)serviceProxy.Execute(requestToCreateRecords); // Display the results of each response. foreach (var responseItem in responseForCreateRecords.Responses) { Console.WriteLine("Created record with GUID {0}", responseItem.Response.Results["id"].ToString()); } }
它是如何工作的
与第一个方法和事务方法类似,我们首先创建了一个新的解决方案,并引用了正确的NuGet包。然后,我们在步骤3中将正确的库引用添加到类文件中。
在步骤4中,我们首先通过从Microsoft.Xrm.Tooling命名空间向CrmServiceClient类注入一个简单的连接字符串来连接Dynamics365。
然后,我们创建了一组请求:创建帐户请求和创建联系人请求。我们将请求集合传递给了ExecuteMultipleRequest类。我们还将ExecuteMultipleSettings属性设置为请求,以便在遇到错误时继续执行并返回所有响应。
我们执行了请求,并最终显示了每个响应的ID,因为我们设置了创建请求以返回创建记录的GUID。
在幕后,SDK正在利用底层SQLtransaction功能。
如果您有Dynamics365的内部部署实例,SQL探查器会话会显示一个批处理启动和批处理完成事件,执行如下:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
还有更多。。。
批处理请求中执行的请求数上限为1000。
如果您尝试执行的批处理大于允许的最大值,则会引发异常。但是,您可以捕获异常,从异常中读取允许的最大数量,并相应地调整批次大小。MSDN的这篇文章重点介绍了检索最大批处理大小的代码:Use ExecuteMultiple to improve performance for bulk data load | Microsoft Learn。
暂存数据导入
大多数企业Dynamics实现都需要从遗留系统进行某种数据迁移。Dynamics365通过其web界面提供开箱即用的文件导入功能。这种传统的数据导入机制通常被批评为设置起来费力且速度缓慢,尤其是在出现错误的情况下。
最近,引入了一个点击式数据加载器web工具,通过允许分段、错误协调和批处理来增强旧功能。在撰写本文时,数据加载器工具仍处于预览模式,不建议用于生产。它也仅在北美Dynamics 365实例中可用。
在这个方法中,我们将使用联系人CSV文件创建一个简单的数据加载。
准备
要使数据加载程序正常工作,您需要一个Office 365用户以及一个具有正确权限的Dynamics 365用户(不一定与Office 365用户相同)来读取/写入要加载到的实体。
如何实施
- 导航到https://integrator.lcs.dynamics.com/DataLoader/Index.
- 单击“登录”并使用Office 365凭据登录。
- 使用您的Office 365凭据登录。
- 单击CRM连接,如下图所示:
- 在CRM连接页面中,单击+号并输入以下详细信息:
CRM用户名:<您的Dynamics 365用户名>
CRM密码:<您的Dynamics 365密码>
然后,单击获取CRM实例 - 加载实例后,选择要连接的正确实例,然后单击“创建”:
- 等待CRM连接页面中的连接状态变为正在运行:
- 返回主屏幕,单击“新建导入”,然后按照以下步骤操作:
- 创建一个新项目,方法是为其命名并选择实例,然后单击“下一步”:
- 在步骤2中,选择您选择的文件格式和实体类型,然后单击上传以根据您的首选格式上传文件。等待文件状态完成后,再单击“下一步”。
- 在步骤3中,定义未自动匹配的映射,然后在对映射感到满意时单击“下一步”。
- 在步骤4中,为您的作业命名,然后单击“启动作业”。
- 创建一个新项目,方法是为其命名并选择实例,然后单击“下一步”:
- 在主屏幕上,单击数据作业,然后选择您刚刚创建的作业。
- 在作业窗口中的STAGING选项卡上,单击Validate:
- 一旦记录被标记为有效,请单击将所有记录导入CRM。
它是如何工作的
我们从步骤5到步骤7开始,只需指定凭据即可创建CRM连接。连接管理器会自动识别我们可以访问的实例。别忘了用您的实际Dynamics 365用户名和密码替换<您的Dynamics 365用户名>和<您的Dynamics 365密码>。
在步骤8中,我们创建了文件导入。更具体地说,在步骤8.2中,我们上传了一个要映射到特定实体的文件。请注意,数据加载可以包括多个文件。在步骤8.3中,数据加载器将尝试将列名自动匹配到正确的目标属性。
如果我们尝试在不使用主键组合的情况下加载数据,我们将收到一条警告消息,说明所有记录都将被创建而不会被打乱(如果找不到记录,则更新或插入):
有关追加销售功能的更多详细信息,请阅读第1章“无代码扩展”中的使用主键的重复检测示例。
到步骤8结束时,我们的数据还没有加载到Dynamics365中;它被暂存以进行验证。
在步骤10中,我们执行了数据验证,以确保所有属性都与模式兼容,并且可以解析所有查找。
我们最终在步骤11中将数据加载到Dynamics365中。
与传统的CSV导入相比,使用数据加载器的优势在于其简单的配置和分段验证功能,允许您重新访问并修复不正确的记录,最重要的是,使用后台批处理请求加载数据的速度。
还有更多。。。
数据加载器服务还提供了一个错误修复功能以及模式刷新功能,以防实例的模式被更新。
修复错误
当您遇到加载错误时,您可以下载包含有问题记录的Excel电子表格,对其进行修改,然后从Excel中将其发布回。使用Excel中的Office 365帐户登录后,数据连接器加载项将允许您使用“发布”按钮将更改发布回Dynamics 365,如以下屏幕截图所示:
刷新实例的架构
当我们在步骤5中创建CRM连接时,实例的元数据已加载。更改Dynamics 365架构时,必须刷新连接。
要做到这一点,请从数据加载器登录页转到您的CRM连接,然后单击刷新按钮。
创建早期绑定的实体类
在服务器端自定义中操作实体记录可以使用早期绑定或后期绑定类来完成。早期绑定的类允许您在编译时使用Visual Studio intellisense和点表示法来访问和验证实体名称和属性名称。另一方面,对于后期绑定实体,开发人员必须手动键入实体和属性的名称,这些名称将在运行时进行验证。尽管如此,后绑定实体具有优势;它们使您能够灵活地编写不绑定到特定实体和属性的通用代码。
准备
为了生成早期绑定的实体类,您需要下载与您的Dynamics 365实例匹配的Dynamics 365 SDK版本。您还需要具有系统自定义程序或更高权限的活动Dynamics 365用户。
如何实施
- 在命令提示符下导航到<SDK文件夹位置>\bin文件夹。
- 运行以下命令:
CrmSvcUtil.exe /connectionstring:"AuthType=Office365; Username=@.onmicrosoft.com; Password=;Url=https://.crm.dynamics.com" /namespace:Packt
它是如何工作的
CrmSrvUtil检查应用于Dynamics365实例的所有架构,并为每个架构生成早期绑定类。
在这个方法中,我们向CrmSrvUtil提供了一组参数:
/conconnectionstring参数保存实例连接字符串。它包括URL、身份验证机制和要连接的凭据。/out参数定义输出类的名称。请确保参数及其值之间没有空格。
类使用的名称空间在/namespace参数中定义(在我们的实例中,我们遵循了与上一个方法相同的约定)。生成代码的语言在/language参数中定义(我们为C#选择了CS,这是默认值;另一种选择是VB)。最后,在/serviceContextName参数中定义了稍后用于运行LINQ查询的服务上下文名称。
还有更多。。。
CrmSvcUtil还有一些其他可以使用的参数。要查看所有可能的参数和进一步的详细信息,包括缩写形式,请在命令提示符中键入CrmSvcUtil/help。
交互式登录
其中包括交互式登录参数(缩写为il)。当设置为true时,您将收到我们在许多其他方法中看到的熟悉登录机制的提示:
您的新命令行应该如下所示:
CrmSvcUtil.exe /namespace:Packt.Xrm.Entities /out:Entities.cs /language:CS /serviceContextN
如果你不想让你的密码以纯文本显示,这是一个很好的做法;但是,作为折衷方案,您必须处理登录提示并以某种方式输入详细信息。
生成操作消息
generateActions参数指示该工具根据第4章“服务器端扩展”中的“创建自定义操作示例”生成自定义操作请求和响应消息。
开发人员工具包实体生成
您可以使用Developer Toolkit Visual Studio加载项逐类定义早期绑定实体,如第4章“服务器端扩展”的“使用Dynamics CRM Developer Toolkit模板示例创建解决方案”中所定义。
扩展CrmSvcUtil
CrmSvcUtil可以通过创建.NET扩展程序集进行扩展。扩展可以用于筛选可以在生成的类中创建的实体,添加选项集枚举,操作生成的代码,等等.
最好的做法是过滤创建的实体的数量,否则,您的类将过大。
在本章稍后的部分中,使用过滤扩展CrmSvcUtil和扩展CrmSvcUtil以生成选项集枚举示例涵盖了几个场景。
参阅
- 用筛选扩展CrmSvcUtil
- 扩展CrmSvcUtil以生成选项集枚举
- 使用第4章“服务器端扩展”中的Dynamics CRM Developer Toolkit模板示例创建解决方案
用筛选扩展CrmSvcUtil
如创建早期绑定实体类中所述,CrmSvcUtil是生成早期绑定实体的一个很好的工具,可以提高开发效率。
CrmSvcUtil按原样生成与Dynamics 365实例关联的所有实体。生成的文件通常很大,检查起来很麻烦。在这个示例中,我们将编写一个小的扩展,只为具有我们选择的前缀的自定义实体生成早期绑定类,在本例中,为packt_prefix(我们的Dynamics365发布者前缀)。
准备
为了创建扩展,您将需要一个IDE,如Visual Studio.NET 4.5.2,对Microsoft.CrmSdk.CoreAssemblies NuGet包的引用,以及对CrmSvcUtil.exe可执行文件的引用。
如何实施
- 使用类型为类库的项目创建一个名为Packt.Xrm.CrmSvcUtilExensions的新Visual Studio解决方案。
- 使用NuGet包管理器,安装与实例相关的Microsoft.CrmSdk.CoreAssemblies的最新版本。,
Generate early-bound classes for the SDK for .NET - Power Apps | Microsoft Learn,
注意:online版本已经使用Power Platform CLI pac auth commands.
只有on-premise版本继续使用CrmSvcUtil工具 - 使用引用管理器,添加对CrmSvcUtil.exe的引用。
- 创建一个名为PacktFilter的新C#类,并插入以下代码:
using System; using System.Collections.Generic; using System.Linq; using Microsoft.Crm.Services.Utility; using Microsoft.Xrm.Sdk.Metadata; namespace Packt.Xrm.CrmSvcUtilExensions { public sealed class PacktFiltering : ICodeWriterFilterService { private ICodeWriterFilterService DefaultService { get; } public PacktFiltering(ICodeWriterFilterService defaultService) { DefaultService = defaultService; } //Omitted code for default behavior interfaces //Custom filtering value defined in GetFilter() // Ensure to only include elements with a schema name that start with the value defined in GetFilter bool ICodeWriterFilterService.GenerateEntity(EntityMetadata entityMetadata, IServiceProvider services) { return entityMetadata.SchemaName.StartsWith(GetFilter()) && DefaultService.GenerateEntity(entityMetadata, services); } //Get the filter value from the command argument private static string GetFilter() { var filterArgument = Environment.GetCommandLineArgs().FirstOrDefault(p => p.ToLower().StartsWith("/filter")); return filterArgument?.Substring(filterArgument.IndexOf(":") + 1).Trim('"').Trim() ?? string.Empty; } } }
- 编译您的解决方案。
- 使用以下参数从bin\Debug文件夹运行CrmSvcUtil命令:
CrmSvcUtil.exe /codewriterfilter:"Packt.Xrm.CrmSvcUtilExensions.PacktFiltering, Packt
它是如何工作的
在步骤1到步骤3中,我们设置解决方案以生成一个可以在CrmSvcUtil命令行中引用的程序集。在步骤3中,我们添加了对CrmSvcUtil.exe可执行文件的引用,该可执行文件包括Microsoft。Crm。服务。实用程序库需要在我们的扩展开发中。可执行文件位于Dynamics 365 SDK的Bin文件夹下。
在步骤4中,我们创建了实现ICodeWriterFilterService的扩展类。这需要实现所有六个接口,其中大多数都引用默认的服务行为(省略代码)。我们更改的唯一方法是GenerateEntity,如果实体的架构名称不是以为/filter命令行参数输入的值packt_开头,则该方法现在返回false。
最后,在步骤6中,我们使用/codewriterfilter参数运行CrmSvcUtil.exe,该参数截取执行管道,并在调用默认行为之前检查每个执行的实体是否以packt_为前缀。筛选器值通过名为/filter的自定义参数传递。
不要忘记用正确的值替换连接字符串。查看以下文章以了解如何构造连接字符串:
Use connection strings in XRM tooling to connect to Dynamics 365 | Microsoft Learn
还有更多。。。
在这个简化的方法中,我们输入了一个简单的过滤器,只包括以特定前缀开头的实体。这可以进一步增强,以包括XML白名单/黑名单,甚至只包括属于特定解决方案的实体。
CrmSvcUtil是一个非常强大的实用程序;下一个方法将介绍如何构建该工具的扩展,以生成类型化的选项集枚举。
考虑将CrmSvcUtil命令包装在批处理文件、Visual Studio扩展名(或外部工具)或其他集成机制中。
扩展CrmSvcUtil以生成选项集枚举
在这个方法中,我们将构建一个CrmSvcUtil扩展,该扩展生成一个干净的类文件,其中包含一组经过筛选的选项集枚举。
准备
就像前面的示例一样,要创建扩展,您需要一个IDE,如Visual Studio with.NET 4.5.2,一个对Microsoft.CrmSdk.CoreAssemblies NuGet包的引用,以及一个对CrmSvcUtil.exe可执行文件的引用。您可以利用在上一个示例中创建的现有解决方案。
如何实施
- 使用与上一个方法相同的解决方案,创建一个名为PacktOptionSetFiltering的新类,代码如下:
using System; using System.Collections.Generic; using System.Linq; using Microsoft.Crm.Services.Utility; using Microsoft.Xrm.Sdk.Metadata; namespace Packt.Xrm.CrmSvcUtilExensions { public sealed class PacktOptionSetFiltering : ICodeWriterFilterService { private ICodeWriterFilterService DefaultService { get; } private Dictionary<string, bool> GeneratedOptionSets { get; } public PacktOptionSetFiltering(ICodeWriterFilterService defaultService) { DefaultService = defaultService; GeneratedOptionSets = new Dictionary<string, bool>(); } //Only include piclists, state, and status attributes bool ICodeWriterFilterService.GenerateAttribute(AttributeMetadata attributeMetadata, IServiceProvider services) { return (attributeMetadata.AttributeType == AttributeTypeCode.Picklist || attributeMetadata.AttributeType == AttributeTypeCode.State || attributeMetadata.AttributeType == AttributeTypeCode.Status); } bool ICodeWriterFilterService.GenerateOptionSet(OptionSetMetadataBase optionSetMetadata, IServiceProvider services) { if (!optionSetMetadata.Name.StartsWith(GetFilter()) || GeneratedOptionSets.ContainsKey(optionSetMetadata.Name)) return false; if (optionSetMetadata.IsGlobal.HasValue && optionSetMetadata.IsGlobal.Value) GeneratedOptionSets[optionSetMetadata.Name] = true; return true; } // Omitted code GenerateServiceContext returns false // GenerateRelationship returns flase // GenerateEntity same as previous recipe // GetFilter same as previous recipe // GenerateOption default behavior } }
- 使用以下代码创建一个名为CodeCustomizationService.cs的新类:
using System; using System.CodeDom; using Microsoft.Crm.Services.Utility; namespace Packt.Xrm.CrmSvcUtilExensions { public sealed class CodeCustomizationService : ICustomizeCodeDomService { public void CustomizeCodeDom(CodeCompileUnit codeUnit, IServiceProvider services) { for (var i = 0; i < codeUnit.Namespaces.Count; ++i) { var types = codeUnit.Namespaces[i].Types; for (var j = 0; j < types.Count;) { if (!types[j].IsEnum || types[j].Members.Count == 0) { types.RemoveAt(j); } else { j += 1; } } } } } }
- 编译您的代码。
- 使用以下参数在bin\Debug文件夹中运行CrmSvcUtil命令:
CrmSvcUtil.exe /codewriterfilter:"Packt.Xrm.CrmSvcUtilExensions.PacktOptionSetFiltering
它是如何工作的。。。
在这个方法中,我们通过更改GenerateAttribute的行为,增强了以前的实体过滤扩展,使其仅包括选项集、状态和状态属性。我们还更改了GenerateOptionSet行为,使所有以我们选择的前缀开头的选项集都返回true(前缀值作为参数根据前面的示例使用/filter传递到命令行),同时确保不会创建重复项。
在步骤2中,我们创建了CodeCustomizationService,它实现了ICustomizeCodeDomService,并更改了CustomizeCodeDom以从生成的代码中删除任何不是枚举或没有成员的内容。这样可以确保只包括枚举。此片段基于SDK提供的代码。
最后,当从命令行调用CrmSvcUtil时,我们将PacktOptionSetFiltering和CodeCustomizationService分别传递给codewriterfilter和codecustomization参数。与前面的方法类似,我们还包含了一个自定义过滤器参数。
结果是一个只包含模式名称以packt开头的枚举的类_没有重复。
还有更多。。。
同样,这个食谱被删减了,可以进一步扩展。例如,SDK包含进一步的扩展,例如通过实现INamingService扩展来自定义生成的属性的名称。示例代码在SDK中的SDK\SampleCode\CS\CrmSvcUtilExtensions下提供。
使用CRM配置迁移工具跨实例迁移配置
通常,当您在具有不同软件开发生命周期(SDLC)环境(开发、测试、用户验收测试、预生产、生产等)的企业应用程序上工作时,您的配置数据需要在不同环境之间保持一致。幸运的是,Dynamics 365 SDK包含一个配置迁移工具,专门用于将少量配置数据从一个环境迁移到另一个环境。
配置迁移工具不是为在环境之间迁移大量数据而设计的。对于大型数据迁移,请考虑其他选择,如开箱即用的CSV导入、数据加载程序web工具,甚至第三方工具,如Scribe和KingswaySoft SSIS包。
在这个方法中,我们将从一个实例导出简单的帐户和联系人数据,并将其导入到另一个实例。
准备
配置迁移工具包含在SDK下的Dynamics 365 SDK中|工具|配置迁移。您还需要对要导出并在目标中写入的实体进行读取访问。
如何实施
- 启动DataMigrationUtility.exe应用程序,该应用程序包含在SDK | Tools | ConfigurationMigration下的Dynamics 365 SDK中。
- 单击“创建架构”,然后单击“继续”:
- 在登录屏幕上,单击登录并输入您的凭据。
- 在创建配置架构上,选择要为其创建架构的解决方案、实体和属性,然后单击保存和导出。您可以包括多个实体:
- 在导出对话框中,为XML文件指定一个名称,并在首选位置进行导航,然后单击保存。
- 当出现对话框要求您导出数据时,单击“是”。
- 点击在“保存到数据文件”字段旁边,输入数据zip文件的名称,然后单击“保存”。
- 在导出数据屏幕中,单击导出数据。
- 导出完成后单击“退出”。
- 在配置迁移实用程序的主页面上,单击导入数据,然后单击继续。
- 按照步骤3登录到您的目标实例。
- 登录后,单击。。。在Zip文件文本字段旁边,选择在步骤8中创建的数据Zip文件,然后单击导入数据。
它是如何工作的。。。
我们通过为正在导出的数据创建模式来启动方法。在步骤3中,我们登录。
注意到日志记录机制如何只要求我们提供凭据并识别我们可以访问的Dynamics365实例?这是连接到Dynamics 365的首选方式,因为它会自动检测访问您的组织的最佳方式。与显式定义web服务端点相比,这是一种最佳实践,后者可能会随着时间的推移而变化。
在步骤4和步骤5中,我们选择了要追求的属性,并导出了模式。这促使我们在步骤6中也导出数据,这是我们在步骤7和步骤8中完成的。
在步骤10到步骤12中,我们将配置数据导入到不同的组织中。
还有更多。。。
配置工具可以迁移使用其他传统方法难以导入的数据。这包括业务单元、用户、主题等等。
参阅
- 暂存数据导入
- 使用第5章“外部集成”的Scribe示例运行无代码计划同步
- 使用第5章“外部集成”中的Kingswaysoft示例与SSIS进行集成