需求背景
新增一个视图,用于查询A实体数据满足如下条件之一的数据
- A实体字段过滤,子记录字段小于等于 当前日期
- A实体字段过滤 或者 A实体关联B实体 字段 字段早于当前日期30天
- A实体字段过滤
由于条件中包含 小于等于当前日期 以及 字段早于当前日期30天 等条件无法对该视图进行配置过滤条件,遂通过开发进行数据过滤。
查阅相关资料找到通过 插件对视图进行过滤方案。参考连接如下(侵删)
博客园:溜溜球_小钢wan 【Dynamics 365 通过插件中的retrievemultiple消息来过滤产品视图】
- 注册在A实体的RetrieveMultiple消息上
if (context.MessageName.ToLower() == "retrievemultiple" && context.InputParameters.Contains("Query"))
{
if (context.InputParameters["Query"] is FetchExpression)
{
var fetchExpression = context.InputParameters["Query"] as FetchExpression;
var conversionRequest = new FetchXmlToQueryExpressionRequest
{
FetchXml = fetchExpression.Query
};
var conversionResponse = (FetchXmlToQueryExpressionResponse)service.Execute(conversionRequest);
QueryExpression query = conversionResponse.Query;
if (query.EntityName == "A实体" && context.Depth == 1)
{
// 代码逻辑
}
}
- 如果仅在1的基础上进行过滤,会对A实体的所有查询进行过滤,当下问题就就变为了如何区分普通查询与指定视图的查询并针对过滤 ( 询问deepseek给出如下答案 但通过我的尝试,在A实体的RetrieveMultiple 消息中 context内容并不包含 viewId属性 且 fetch中也不包含 layoutxml、name等属性)context.InputParameters内容以及 deepseek的获取方法如下
在 Dynamics 365 插件中获取当前视图信息 (RetrieveMultiple 消息)
在我插件中 打印出的context.InputParameters内容(attribute 和linkentity 节点均已删除)
[
{
"Key": "Query",
"Value": {
"Query": "<fetch version=\"1.0\" output-format=\"xml-platform\" mapping=\"logical\" returntotalrecordcount=\"true\" page=\"1\" count=\"250\" no-lock=\"false\"><entity name=\"A实体\"></entity></fetch>"
}
},
{
"Key": "AppModuleId",
"Value": "00000000-0000-0000-0000-000000000000"
},
{
"Key": "IsAppModuleContext",
"Value": false
}
]
1. 从插件上下文中获取视图ID
if (context.InputParameters.Contains("Query") &&
context.InputParameters["Query"] is FetchExpression fetchExpression)
{
// 获取视图ID(如果可用)
if (context.InputParameters.Contains("viewId"))
{
Guid viewId = (Guid)context.InputParameters["viewId"];
}
}
- 从 QueryExpression 或 FetchExpression 中获取视图信息
if (context.MessageName == "RetrieveMultiple" && context.InputParameters.Contains("Query"))
{
QueryBase query = (QueryBase)context.InputParameters["Query"];
// 情况1:查询是基于视图的FetchXML
if (query is FetchExpression fetchExpression)
{
string fetchXml = fetchExpression.Query;
// 解析FetchXML获取视图信息
XmlDocument doc = new XmlDocument();
doc.LoadXml(fetchXml);
// 检查是否包含视图ID
XmlNode layoutXmlNode = doc.SelectSingleNode("//layoutxml");
if (layoutXmlNode != null)
{
// 这是一个基于视图的查询
string layoutXml = layoutXmlNode.InnerText;
// 可以从layoutXml中解析更多视图信息
}
// 检查是否包含视图名称
XmlNode nameNode = doc.SelectSingleNode("//name");
if (nameNode != null)
{
string viewName = nameNode.InnerText;
// 现在您知道了视图名称
}
}
// 情况2:查询是QueryExpression
else if (query is QueryExpression queryExpression)
{
// QueryExpression通常不直接包含视图信息
// 但可以检查EntityName和其他属性
string entityName = queryExpression.EntityName;
// 其他查询属性...
}
}
- 正式解决方案
1、获取到查询的fetch后转换为 QueryExpression
2、查询需要过滤视图的fetch,并转换为 QueryExpression
3、对比当前查询的 QueryExpression 与 需要过滤视图的 QueryExpression 的字段、条件、关联实体 等是否都符合
缺点
- 用户在视图界面添加过滤会导致 本过滤失效(本过滤匹配条件是 查询条件 = 预设视图条件(无条件))
- 执行ExportToExcel 操作会导出全量数据,此代码未对该操作进行过滤
可优化点
- 实现方式:在实体新增一个标识字段,将其仅添加到需过滤视图。前台隐藏->在 XrmToolBox里的View Designer工具通过编辑视图XML文件的方式将该字段删除,后台在retrievemultiple中捕获查询 按照该字段进行比对并进行过滤
- ExportToExcel 导出到表格的数据也会进入当前插件,可捕获后进行过滤。或针对context.MessageName.Equals(“ExportToExcel”) 的消息单独进行过滤
- 本次实现因过滤较为复杂 将符合条件数据查询后通过 in 的方式进行过滤,如果查询条件不复杂可直接将条件添加到查询的fetch中
View Designer工具示例
以下是【正式解决方案】实现实现代码
public override void ExecuteCrmPlugin(XrmLocalPluginContext localContext)
{
if (localContext == null)
throw new ArgumentNullException(nameof(localContext));
else
{
//模板
ITracingService tracer = localContext.TracingService;
IPluginExecutionContext context = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;
IOrganizationService serviceAdmin = localContext.OrganizationAdminService;
if (context.MessageName.ToLower() == "retrievemultiple" && context.InputParameters.Contains("Query"))
{
if (context.InputParameters["Query"] is FetchExpression)
{
var fetchExpression = context.InputParameters["Query"] as FetchExpression;
var conversionRequest = new FetchXmlToQueryExpressionRequest
{
FetchXml = fetchExpression.Query
};
var conversionResponse = (FetchXmlToQueryExpressionResponse)service.Execute(conversionRequest);
QueryExpression query = conversionResponse.Query;
if (query.EntityName == "A实体" && context.Depth == 1)
{
// 查询指定视图
QueryExpression view_query = new QueryExpression("savedquery")
{
ColumnSet = new ColumnSet("fetchxml", "savedqueryid"),
Criteria =
{
Conditions =
{
new ConditionExpression("savedqueryid",ConditionOperator.Equal,"视图guid")
}
}
};
var viewction = service.RetrieveMultiple(view_query);
if (viewction.Entities.Count > 0)
{
var view_fetch = new FetchExpression(viewction.Entities[0].GetAttributeValue<string>("fetchxml"));
var view_conversionRequest = new FetchXmlToQueryExpressionRequest
{
FetchXml = view_fetch.Query
};
var view_conversionResponse = (FetchXmlToQueryExpressionResponse)service.Execute(view_conversionRequest);
// 查询到的指定视图 QueryExpression
QueryExpression v_query = view_conversionResponse.Query;
if (AreEqual(query, v_query))
{
var allguids = new List<Guid>();
#region 筛选条件
#region 条件1
string fetchxml1 = $@"替换为实际的过滤语句";
EntityCollection ent_ction1= service.RetrieveMultiple(new FetchExpression(fetchxml1));
#endregion
if (ent_ction1.Entities.Count > 0)
{
foreach (var item in ent_ction1.Entities)
{
if (!allguids.Contains(item.Id))
{
allguids.Add(item.Id);
}
}
}
#region 条件2
string fetchXML2 = $@"替换为实际的过滤语句";
EntityCollection ent_ction2= service.RetrieveMultiple(new FetchExpression(fetchXML2));
#endregion
if (ent_ction2.Entities.Count > 0)
{
foreach (var item in ent_ction2.Entities)
{
if (!allguids.Contains(item.Id))
{
allguids.Add(item.Id);
}
}
}
#endregion
// 添加过滤条件
query.Criteria.AddCondition(new ConditionExpression("实体主键", ConditionOperator.In, allguids));
var converRequest = new QueryExpressionToFetchXmlRequest
{
Query = query
};
var converResponse = (QueryExpressionToFetchXmlResponse)service.Execute(converRequest);
String fetchXml = converResponse.FetchXml;
fetchExpression.Query = fetchXml;
}
}
}
}
}
}
}
public static bool AreEqual(QueryExpression qe1, QueryExpression qe2)
{
if (qe1 == null || qe2 == null) return qe1 == qe2;
// 1. 基础属性
if (qe1.EntityName != qe2.EntityName) return false;
if (qe1.Distinct != qe2.Distinct) return false;
if (qe1.TopCount != qe2.TopCount) return false;
if (qe1.NoLock != qe2.NoLock) return false;
// if (!PageInfoEqual(qe1.PageInfo, qe2.PageInfo)) return false;
// 视图查询列集 较 视图定义列集 增加了 statecode 字段,移除比较
qe1.ColumnSet.Columns.Remove("statecode");
// 2. 列集
if (!ColumnSetsEqual(qe1.ColumnSet, qe2.ColumnSet)) return false;
// 3. 条件
if (!CriteriaEqual(qe1.Criteria, qe2.Criteria)) return false;
// 4. 排序
// if (!OrdersEqual(qe1.Orders, qe2.Orders)) return false;
// 5. 关联实体
if (!LinkEntitiesEqual(qe1.LinkEntities, qe2.LinkEntities)) return false;
return true;
}
// 比较分页信息
private static bool PageInfoEqual(PagingInfo p1, PagingInfo p2)
{
if (p1 == null || p2 == null) return p1 == p2;
return p1.PageNumber == p2.PageNumber &&
p1.Count == p2.Count &&
p1.PagingCookie == p2.PagingCookie;
}
// 比较列集
private static bool ColumnSetsEqual(ColumnSet cs1, ColumnSet cs2)
{
if (cs1 == null || cs2 == null) return cs1 == cs2;
if (cs1.AllColumns != cs2.AllColumns) return false;
return cs1.Columns.Count == cs2.Columns.Count &&
cs1.Columns.All(cs2.Columns.Contains);
}
// 比较条件(递归)
private static bool CriteriaEqual(FilterExpression f1, FilterExpression f2)
{
if (f1.FilterOperator != f2.FilterOperator) return false;
if (f1.Conditions.Count != f2.Conditions.Count) return false;
// 比较条件表达式
foreach (var c1 in f1.Conditions)
{
var c2 = f2.Conditions.FirstOrDefault(c =>
c.AttributeName == c1.AttributeName &&
c.Operator == c1.Operator &&
c.Values.SequenceEqual(c1.Values));
if (c2 == null) return false;
}
// 递归比较子条件
if (f1.Filters.Count != f2.Filters.Count) return false;
for (int i = 0; i < f1.Filters.Count; i++)
{
if (!CriteriaEqual(f1.Filters[i], f2.Filters[i])) return false;
}
return true;
}
// 比较关联实体(递归)
private static bool LinkEntitiesEqual(DataCollection<LinkEntity> le1, DataCollection<LinkEntity> le2)
{
if (le1.Count != le2.Count) return false;
for (int i = 0; i < le1.Count; i++)
{
var l1 = le1[i];
var l2 = le2[i];
if (l1.LinkToEntityName != l2.LinkToEntityName ||
l1.LinkFromAttributeName != l2.LinkFromAttributeName ||
l1.LinkToAttributeName != l2.LinkToAttributeName ||
!ColumnSetsEqual(l1.Columns, l2.Columns) ||
!CriteriaEqual(l1.LinkCriteria, l2.LinkCriteria))
return false;
// 递归比较嵌套关联实体
if (!LinkEntitiesEqual(l1.LinkEntities, l2.LinkEntities)) return false;
}
return true;
}
// 比较排序
private static bool OrdersEqual(DataCollection<OrderExpression> o1, DataCollection<OrderExpression> o2)
{
if (o1.Count != o2.Count) return false;
for (int i = 0; i < o1.Count; i++)
{
if (o1[i].AttributeName != o2[i].AttributeName ||
o1[i].OrderType != o2[i].OrderType)
return false;
}
return true;
}
524

被折叠的 条评论
为什么被折叠?



