如何基于异步C# OData提升Dynamics CRM的处理吞吐量?
你当前的POC已经比SSIS快400%,但结合Dynamics CRM OData WebAPI的特性和服务器配置,仍有不少优化空间,针对删除操作给出以下具体建议:
1. 充分利用连接池,调整HttpClient连接限制
你当前GetClient()创建的HttpClient,默认的MaxConnectionsPerServer可能较低(.NET Framework默认是2,.NET Core/5+虽无硬限制,但实际受服务器约束),而服务器允许100个连接,手动调整这个参数能让单个HttpClient利用更多并发连接:
var httpHandler = new HttpClientHandler { Credentials = new NetworkCredential(crmUsername, crmPassword, crmDomain), MaxConnectionsPerServer = 100 // 匹配服务器连接限制 };
注意保持HttpClient全局复用(你当前代码已经做到,不要在循环内重复创建)。
2. 提高并行度,匹配服务器连接能力
你当前设置的并行度为4,远低于服务器的100连接上限。可以逐步提高MaximumDegreeOfParallelism的值,先从10-20开始测试,再根据吞吐量调整(建议不超过服务器连接数的80%,避免触发节流):
await Parallel.ForEachAsync(entityList.Chunk(batchMaximumSize ), new ParallelOptions { MaximumDegreeOfParallelism = 15 }, async (chunk, _) => { // 原batch逻辑不变 });
3. 优化OData批量删除请求
跳过ETag检查
删除操作默认会验证实体的ETag,增加服务器处理开销。可以在Delete请求中添加If-None-Match: *头跳过检查:
batch += oDataClient => oDataClient.For<Entity>() .Key(entity.Id) .WithHeader("If-None-Match", "*") .DeleteEntryAsync(_);
验证Batch请求容量上限
你测试了100-500的batch大小无差异,但可以尝试将batch size提高到1000(Dynamics CRM通常允许单个batch包含最多1000个操作),看是否能提升效率。
4. 使用多服务用户突破单用户并发限制
Dynamics CRM对单个用户的并发请求有默认限制(通常10-20),即使服务器允许100连接,单用户也无法充分利用。你可以创建多个服务用户,为每个用户单独创建HttpClient实例,再分发任务:
// 假设有4个服务用户的凭据列表 var userCredentials = new List<(string Username, string Password)> { ("user1", "pass1"), ("user2", "pass2"), ("user3", "pass3"), ("user4", "pass4") }; // 为每个用户创建客户端 var clients = userCredentials.Select(cred => GetClient(cred.Username, cred.Password)).ToList(); // 按客户端分发任务 await Parallel.ForEachAsync(entityList.Chunk(batchMaximumSize ), new ParallelOptions { MaximumDegreeOfParallelism = clients.Count * 5 }, async (chunk, _) => { // 轮询或随机选择客户端 var client = clients[Random.Shared.Next(clients.Count)]; var batch = new ODataBatch(client); // 后续删除逻辑不变 });
修改GetClient()支持传入用户名密码:
public static IODataClient GetClient(string username, string password) { const string baseAddress = "http://crm-address/"; const string apiUrl = "api/data/v8.2"; var crmDomain = Environment.GetEnvironmentVariable("DOMAIN"); var httpHandler = new HttpClientHandler { Credentials = new NetworkCredential(username, password, crmDomain), MaxConnectionsPerServer = 25 // 每个用户分配25连接,4个用户刚好占满100上限 }; var httpClient = new HttpClient(httpHandler) { BaseAddress = new Uri(baseAddress), }; var odataSettings = new ODataClientSettings(httpClient, new Uri(apiUrl, UriKind.Relative)); odataSettings.IgnoreResourceNotFoundException = true; return new ODataClient(odataSettings); }
5. 优化实体获取逻辑
如果GetEntitiesForDeletion()返回的是完整实体对象,建议只查询Id字段即可(删除仅需要Id),减少数据传输和内存占用:
// 示例:仅获取删除所需的Id var entityList = await oDataClient.For<Entity>() .Select(e => e.Id) .Filter(/* 你的删除条件 */) .FindEntriesAsync();
6. 排查服务器端性能瓶颈
- 检查CRM组织的节流设置:管理员可在CRM后台查看是否有并发请求限制,是否可调整;
- 禁用非必要插件/触发器:实体的删除前后插件会大幅增加耗时,建议暂时禁用非核心插件测试性能;
- 确认数据库状态:查看SQL Server的CPU、磁盘IO是否有瓶颈,是否需要优化索引。
关于OData WebAPI的速度限制
OData WebAPI本身确实存在性能上限(对比直接SQL操作),但通过上述优化应该能显著提升吞吐量。如果最终仍无法满足需求,可以考虑改用Organization Service的ExecuteMultipleRequest,它在批量操作上通常比OData WebAPI更高效。
内容的提问来源于stack exchange,提问作者RBP

