You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

D365 CE中OrganizationServiceContext与QueryExpression查询结果差异原因

问题描述

我正在尝试在插件中使用OrganizationServiceContext执行LINQ查询以检索若干报价单,通过.Select()方法仅选取cgk_totalnetprice字段的值,代码如下:

quotes = OrganizationServiceContext.QuoteSet
   .Where(_ =>
      _.OpportunityId != null &&
      _.OpportunityId.Id == opportunityId &&
      _.QuoteId != currentQuote.Id &&
      (_.StatusCode.Value == (int)Quote_StatusCode.Won || _.StatusCode.Value == (int)Quote_StatusCode.WonOrder) &&
      _.cgk_quotetypecode != null &&
      (_.cgk_quotetypecode.Value == (int)QuoteTypeCode.Regular || _.cgk_quotetypecode.Value == (int)QuoteTypeCode.ServiceUnderWarranty))
   .Select(x => new Quote() { Id = x.Id, cgk_totalnetprice = x.cgk_totalnetprice})
   .ToList();

然而在检索这些报价单时,除了一份报价单外,上下文返回的其他所有报价单的该字段都没有值(且该份有值的报价单并非触发本次更新的记录,只是一份完全未被更新的随机记录)。
奇怪的是,当我将查询改写为QueryExpression写法时,一切运行完全正常:

QueryExpression qe = new QueryExpression("quote");
//Exclude current quote
qe.Criteria.AddCondition("quoteid", ConditionOperator.NotEqual, currentQuote.Id);
//Opportunity
qe.Criteria.AddCondition("opportunityid", ConditionOperator.NotNull);
qe.Criteria.AddCondition("opportunityid", ConditionOperator.Equal, opportunityId);
//State-Status
FilterExpression statusFilter = new FilterExpression(LogicalOperator.Or);
statusFilter.AddCondition("statuscode", ConditionOperator.Equal, (int)Quote_StatusCode.Won);
statusFilter.AddCondition("statuscode", ConditionOperator.Equal, (int)Quote_StatusCode.WonOrder);
qe.Criteria.AddFilter(statusFilter);
//QuoteType
qe.Criteria.AddCondition("cgk_quotetypecode", ConditionOperator.NotNull);
FilterExpression typeFilter = new FilterExpression(LogicalOperator.Or);
typeFilter.AddCondition("cgk_quotetypecode", ConditionOperator.Equal, (int)QuoteTypeCode.Regular);
typeFilter.AddCondition("cgk_quotetypecode", ConditionOperator.Equal, (int)QuoteTypeCode.ServiceUnderWarranty);
qe.Criteria.AddFilter(typeFilter);
qe.ColumnSet = new ColumnSet("quoteid", "cgk_totalnetprice");
quotes = this.OrganizationService.RetrieveMultiple(qe).Entities.Cast<Quote>().ToList();

请问是什么原因导致OrganizationServiceContext查询与OrganizationService+QueryExpression查询出现这种差异?


原因说明

这是Dataverse/Dynamics 365 SDK中OrganizationServiceContext的默认缓存机制导致的,核心有两点:

  1. 上下文默认启用身份映射缓存:所有通过该上下文加载过的实体都会被缓存在本地,后续再查询到相同ID的实体时,默认只会返回缓存中的旧实体,不会用服务端返回的新字段覆盖已缓存的实体。如果这些报价单之前已经被当前上下文加载过,且当时没有请求cgk_totalnetprice字段,后续哪怕查询指定了这个字段,返回的依然是缓存里字段为空的旧实体。你观察到只有一份报价单有值,就是因为这份实体之前没有被加载到缓存里,是这次查询新增到缓存的,所以带了字段值。
  2. 上下文默认的MergeOption是AppendOnly:该配置下仅会把首次查询到的实体加入缓存,后续查询到相同实体时不会更新已缓存实体的属性。而直接调用OrganizationService.RetrieveMultiple是绕过上下文缓存的,每次直接返回服务端的完整响应,所以不会出现字段丢失的问题。

解决方案

你可以任选一种方式修复:

  • 执行LINQ查询前修改上下文的合并配置,跳过缓存:
OrganizationServiceContext.MergeOption = Microsoft.Xrm.Sdk.Client.MergeOption.NoTracking;
  • 改写成投影到匿名类后再转换为强类型实体,避开缓存匹配逻辑:
quotes = OrganizationServiceContext.QuoteSet
   .Where(/* 原有过滤条件不变 */)
   .Select(x => new { x.Id, x.cgk_totalnetprice })
   .ToList()
   .Select(item => new Quote { Id = item.Id, cgk_totalnetprice = item.cgk_totalnetprice })
   .ToList();
  • 如果不需要用上下文做后续更新操作,查询完成后直接销毁上下文实例,下次查询新建上下文即可,从根源避免缓存累积问题。

内容的提问来源于stack exchange,提问作者Pieter Dubois

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 09:45:05