C# 与 Dynamics 365 深度集成:从基础连接到高级自动化

张开发
2026/4/5 20:45:01 15 分钟阅读

分享文章

C# 与 Dynamics 365 深度集成:从基础连接到高级自动化
1. 环境准备与基础连接第一次接触Dynamics 365的开发时我花了两天时间才搞定开发环境搭建。现在回想起来其实只要掌握几个关键点就能少走弯路。首先需要准备的是Visual Studio 2022社区版免费且功能完整安装时务必勾选.NET桌面开发和.NET跨平台开发两个工作负载。有个容易忽略的细节是.NET Framework 4.8开发工具包需要单独在单个组件选项卡中勾选否则后面运行插件时会报错。连接Dynamics 365时最常遇到的坑是认证问题。我推荐使用CrmServiceClient这个神器它封装了各种认证方式的处理逻辑。最新版的连接字符串格式是这样的var connectionString Urlhttps://yourorg.crm.dynamics.com; AuthTypeOAuth; Usernameuserdomain.com; Passwordyourpassword; ClientId51f81489-12ee-4a9e-aaae-a2591f45987d; RedirectUriapp://58145B91-0C36-4500-8554-080854F2AC97; LoginPromptAuto;这里ClientId是微软默认的客户端IDRedirectUri可以保持这个固定值。如果遇到证书错误可以在代码前加上这句ServicePointManager.SecurityProtocol SecurityProtocolType.Tls12;建立连接后建议立即测试连接状态并缓存服务对象。我通常这样封装public class CRMService { private static CrmServiceClient _instance; public static IOrganizationService GetService() { if(_instance null || !_instance.IsReady) { _instance new CrmServiceClient(connectionString); if(!_instance.IsReady) throw new Exception($连接失败: {_instance.LastCrmError}); } return _instance; } }2. 实体操作实战技巧刚开始操作实体时我总被各种字段类型搞得晕头转向。后来发现Dynamics 365的字段类型与C#类型映射有个简单规律单行文本对应string选项集对应OptionSetValue查找字段对应EntityReference。处理日期时要特别注意时区问题我建议统一使用UTC时间entity[createdon] DateTime.UtcNow;批量操作数据时ExecuteMultipleRequest能大幅提升性能。有次我需要导入5000条客户数据用单条插入要20分钟改用批量后只要35秒var request new ExecuteMultipleRequest() { Settings new ExecuteMultipleSettings() { ContinueOnError true, ReturnResponses true }, Requests new OrganizationRequestCollection() }; for(int i0; i5000; i){ var createRequest new CreateRequest(); createRequest.Target new Entity(account) { [name] $客户{i} }; request.Requests.Add(createRequest); } var response (ExecuteMultipleResponse)service.Execute(request);查询数据时QueryExpression比FetchXML更直观。我常用的查询模式是这样的var query new QueryExpression(account); query.ColumnSet.AddColumns(name, accountnumber); query.Criteria.AddCondition(statecode, ConditionOperator.Equal, 0); // 只查活跃账户 // 关联查询联系人 var contactLink query.AddLink(contact, primarycontactid, contactid); contactLink.Columns.AddColumns(fullname, email); contactLink.EntityAlias con; var results service.RetrieveMultiple(query);3. 插件开发进阶实战写第一个插件时我踩过不少坑。最重要的经验是插件必须放在GAC中或者使用插件注册工具注册。推荐使用ILMerge把依赖项打包成一个DLL这样部署更方便。典型的插件结构应该是这样的public class AccountPlugin : 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); if(context.MessageName Create context.PrimaryEntityName account) { var target (Entity)context.InputParameters[Target]; // 业务逻辑 } } }调试插件有个小技巧在代码中加上这个语句可以附加调试器System.Diagnostics.Debugger.Launch();处理图片上传这类操作时记得检查插件执行上下文深度防止死循环if(context.Depth 1) return;4. 工作流与自定义活动标准工作流满足不了需求时可以开发自定义工作流活动。我开发过一个审批流程活动核心代码如下public class ApprovalActivity : CodeActivity { [Input(审批人)] [ReferenceTarget(systemuser)] public InArgumentEntityReference Approver { get; set; } [Output(审批结果)] public OutArgumentbool IsApproved { get; set; } protected override void Execute(CodeActivityContext context) { var approver Approver.Get(context); var service context.GetExtensionIOrganizationServiceFactory() .CreateOrganizationService(null); // 发送审批邮件 var email new Entity(email); // 设置邮件内容... service.Create(email); // 模拟审批结果 IsApproved.Set(context, new Random().Next(0, 100) 30); } }异步工作流处理长时间任务特别有用。我常用模式是主工作流创建异步任务异步任务完成后触发插件插件唤醒原工作流继续执行5. Web API集成技巧Dynamics 365的Web API功能强大但有些坑要注意。首先API版本号必须精确匹配var client new HttpClient(); client.BaseAddress new Uri(https://yourorg.api.crm.dynamics.com/api/data/v9.2/);处理分页时必须检查odata.nextLinkvar result new ListJToken(); var response await client.GetAsync(accounts?$selectname); var content await response.Content.ReadAsStringAsync(); var json JObject.Parse(content); result.AddRange(json[value]); while(json[odata.nextLink] ! null) { response await client.GetAsync(json[odata.nextLink].ToString()); content await response.Content.ReadAsStringAsync(); json JObject.Parse(content); result.AddRange(json[value]); }批量操作使用$batch端点能极大提升性能var batchContent new MultipartContent(mixed, batch_ Guid.NewGuid()); // 添加多个操作到batchContent var response await client.PostAsync($batch, batchContent);6. 性能优化实战经验经过多次性能调优我总结出几个关键点查询优化始终只查询需要的字段// 错误做法查询所有字段 var entity service.Retrieve(account, id, new ColumnSet(true)); // 正确做法明确指定字段 var entity service.Retrieve(account, id, new ColumnSet(name, accountnumber));批量操作使用ExecuteMultiple插件优化避免在循环中查询数据索引策略为常用查询字段创建索引我开发的一个报表生成功能优化前后对比优化措施执行时间(5000条数据)内存占用原始版本78秒1.2GB添加预加载45秒800MB使用并行处理22秒1.5GB最终优化版18秒650MB7. 安全最佳实践安全配置容易被忽视。我建议字段级安全敏感字段设置字段安全配置文件var fieldPermission new Entity(fieldpermission); fieldPermission[fieldname] accountnumber; fieldPermission[canread] true; fieldPermission[canupdate] false; service.Create(fieldPermission);业务单元隔离确保数据隔离审计日志关键操作记录审计日志var audit new Entity(audit); audit[operation] Update; audit[entityname] account; service.Create(audit);插件权限控制插件执行权限8. 异常处理与调试稳定的异常处理是系统健壮性的关键。我的异常处理模板try { // 业务代码 } catch(FaultExceptionOrganizationServiceFault ex) { // 处理Dynamics特有错误 logger.Error($CRM错误: {ex.Detail.ErrorCode} - {ex.Detail.Message}); throw new Exception(业务处理失败请稍后重试); } catch(Exception ex) { // 记录完整堆栈 logger.Error(ex, 系统异常); throw; }调试时我常用这些工具组合Plug-in Trace LogFiddler抓包Diagnostic工具自定义日志系统9. 部署与DevOps自动化部署能节省大量时间。我的部署脚本包含插件注册Register-XrmPlugin -Path bin\Debug\MyPlugin.dll -SolutionName MySolution -StepName Account Create -Message Create -Entity account解决方案导入配置更新数据初始化在CI/CD流程中建议分三个阶段开发环境自动部署每次提交测试环境手动触发部署生产环境审批后部署10. 复杂业务场景实现最近实现的一个销售自动化流程包含这些关键点机会阶段变更触发产品推荐if(context.PostEntityImages[Opportunity].GetAttributeValueOptionSetValue(salesstage).Value 3) { // 获取推荐产品逻辑 }价格计算使用自定义工作流活动审批流程集成SharePoint订单确认后同步到ERP系统处理并发时我采用乐观并发控制var request new UpdateRequest { Target entity, ConcurrencyBehavior ConcurrencyBehavior.IfRowVersionMatches };11. 自定义UI集成将自定义页面嵌入Dynamics 365表单能增强用户体验。我的实现步骤创建HTML资源div idcustomView !-- 自定义内容 -- /div添加Web资源到表单使用客户端API交互function refreshData() { Xrm.WebApi.retrieveRecord(account, Xrm.Page.data.entity.getId()) .then(function(result){ document.getElementById(customView).innerHTML result.name; }); }12. 测试策略与实践有效的测试应该包含单元测试测试插件逻辑[TestMethod] public void TestAccountPlugin() { var plugin new AccountPlugin(); var context new MockIPluginExecutionContext(); // 设置context参数... plugin.Execute(context.Object); // 断言... }集成测试测试完整流程性能测试使用Visual Studio负载测试UI测试使用Selenium13. 扩展与未来演进随着业务发展系统可能需要与Power Platform集成添加AI功能如预测分析迁移到Dynamics 365 Customer Engagement实现微服务架构最近我将部分逻辑迁移到了Azure Functions效果很好[FunctionName(ProcessOrder)] public static async Task Run([QueueTrigger(orders)]Order order) { // 处理订单逻辑 }14. 常见问题解决方案这些是我遇到最多的问题及解决方法连接超时调整Timeout设置service.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode X509CertificateValidationMode.None; service.Timeout TimeSpan.FromMinutes(5);内存泄漏确保及时释放资源性能下降检查索引和查询认证失败更新ADAL库到最新版15. 监控与维护完善的监控系统应该包含性能计数器监控异常报警定期健康检查容量规划我实现的健康检查页面public ActionResult HealthCheck() { var result new { Database TestDatabaseConnection(), Services TestServices(), Storage TestStorage() }; return Json(result, JsonRequestBehavior.AllowGet); }16. 项目经验分享最近完成的一个跨国项目教会我多时区处理要统一使用UTC多语言实现使用自定义资源文件大数据量操作要分批次处理团队协作要统一开发规范项目中的技术选型前端React嵌入Dynamics表单后端.NET Core微服务数据库Azure SQL集成Azure Service Bus17. 性能调优案例一个实际调优案例问题客户列表加载需要12秒 分析查询了不必要字段缺少索引 解决优化查询字段添加复合索引实现分页加载 结果加载时间降至1.3秒18. 安全加固实践安全审计后发现的问题及修复敏感数据未加密 → 实现字段级加密密码策略弱 → 启用多因素认证审计日志不全 → 完善日志记录API无速率限制 → 添加API网关19. 移动端集成方案实现移动访问的几种方式Dynamics 365移动App自定义Xamarin应用响应式Web界面Power Apps定制Xamarin中调用Web API的示例var client new HttpClient(); client.DefaultRequestHeaders.Authorization new AuthenticationHeaderValue(Bearer, token); var response await client.GetAsync(https://yourorg.api.crm.dynamics.com/api/data/v9.2/accounts);20. 持续学习资源推荐保持技术更新的途径官方文档Microsoft Docs社区论坛Power Platform社区技术博客微软技术博客认证考试MB-400最有价值的三本书Microsoft Dynamics 365开发指南.NET企业级应用架构设计云计算集成模式

更多文章