Dynamics CRM 365插件:如何将QueryExpression应用于自定义实体列表或转LINQ?
首先明确说:你不能直接把QueryExpression实例应用到内存里的List<Entity>,因为QueryExpression是Dynamics 365 SDK专门设计给IOrganizationService.RetrieveMultiple()方法使用的查询对象,它的语法和逻辑是针对CRM后端数据存储的,无法直接作用于内存集合。
不过你完全可以把QueryExpression转换成等价的LINQ表达式,然后用它来过滤、排序你的myList。下面是具体的实现思路和示例:
核心思路:解析QueryExpression并构建LINQ表达式
QueryExpression主要包含几个关键部分:Criteria(过滤条件)、Orders(排序规则)、ColumnSet(返回列),我们需要把这些部分逐一转换成对应的LINQ操作:
1. 处理过滤条件(Criteria)
你需要遍历query.Criteria.Conditions集合,把每个ConditionExpression转换成LINQ的Where子句条件。比如一个ConditionOperator.Equal类型的条件,对应LINQ里的entity.GetAttributeValue<string>("accountname") == "Contoso"。
这里有个简化的示例方法,用来把单个ConditionExpression转换成谓词:
private static Func<Entity, bool> ConvertConditionToPredicate(ConditionExpression condition) { return entity => { var value = entity.GetAttributeValue<object>(condition.AttributeName); return condition.Operator switch { ConditionOperator.Equal => Equals(value, condition.Values[0]), ConditionOperator.GreaterThan => Comparer<object>.Default.Compare(value, condition.Values[0]) > 0, ConditionOperator.LessThan => Comparer<object>.Default.Compare(value, condition.Values[0]) < 0, // 你可以根据需要扩展更多操作符 _ => throw new NotSupportedException($"Condition operator {condition.Operator} is not supported") }; }; }
然后把所有条件组合起来(注意query.Criteria.FilterOperator是And还是Or),生成最终的Where谓词。
2. 处理排序规则(Orders)
遍历query.Orders集合,把每个OrderExpression转换成OrderBy或ThenBy操作:
IOrderedEnumerable<Entity> orderedList = query.Orders.First().OrderType == OrderType.Ascending ? myList.OrderBy(e => e.GetAttributeValue<object>(query.Orders.First().AttributeName)) : myList.OrderByDescending(e => e.GetAttributeValue<object>(query.Orders.First().AttributeName)); foreach (var order in query.Orders.Skip(1)) { orderedList = order.OrderType == OrderType.Ascending ? orderedList.ThenBy(e => e.GetAttributeValue<object>(order.AttributeName)) : orderedList.ThenByDescending(e => e.GetAttributeValue<object>(order.AttributeName)); }
3. 处理列投影(ColumnSet)
如果query.ColumnSet不是AllColumns,你可以用Select来只保留需要的属性,但因为Entity是动态类型,你可能需要创建匿名类型或者自定义DTO来存储结果:
var projectedList = orderedList.Select(e => new { AccountName = e.GetAttributeValue<string>("accountname"), Revenue = e.GetAttributeValue<decimal?>("revenue") // 根据ColumnSet里的属性逐一添加 });
注意事项
- 复杂的QueryExpression特性(比如
LinkEntity关联查询、聚合函数、嵌套过滤)很难直接转换成LINQ,因为内存列表里没有关联的实体数据,这类场景你可能需要额外处理关联逻辑。 - 对于
ConditionOperator的所有枚举值,你需要根据业务需求逐一实现转换,上面的示例只覆盖了常见的几种。 - 如果你的列表很大,转换后的LINQ查询会在内存中执行,要注意性能问题。
完整简化示例
// 假设我们已经有了myList和query var filteredList = myList.AsQueryable(); // 应用过滤条件 if (query.Criteria.Conditions.Any()) { var predicates = query.Criteria.Conditions.Select(ConvertConditionToPredicate); Func<Entity, bool> combinedPredicate; if (query.Criteria.FilterOperator == LogicalOperator.And) { combinedPredicate = e => predicates.All(p => p(e)); } else { combinedPredicate = e => predicates.Any(p => p(e)); } filteredList = filteredList.Where(combinedPredicate); } // 应用排序 if (query.Orders.Any()) { filteredList = ApplySorting(filteredList, query.Orders); } // 最终结果 var result = filteredList.ToList(); // 辅助方法:应用排序 private static IQueryable<Entity> ApplySorting(IQueryable<Entity> source, IEnumerable<OrderExpression> orders) { var firstOrder = orders.First(); var ordered = firstOrder.OrderType == OrderType.Ascending ? source.OrderBy(e => e.GetAttributeValue<object>(firstOrder.AttributeName)) : source.OrderByDescending(e => e.GetAttributeValue<object>(firstOrder.AttributeName)); foreach (var order in orders.Skip(1)) { ordered = order.OrderType == OrderType.Ascending ? ordered.ThenBy(e => e.GetAttributeValue<object>(order.AttributeName)) : ordered.ThenByDescending(e => e.GetAttributeValue<object>(order.AttributeName)); } return ordered; }
内容的提问来源于stack exchange,提问作者noobie

