private static OrderOData ConvertOrder(string entityName, FetchOrderType orderitem, FetchXmlBuilder sender) { if (!String.IsNullOrEmpty(orderitem.alias)) { throw new ApplicationException($"OData queries do not support ordering on link entities. Please remove the sort on {orderitem.alias}.{orderitem.attribute}"); } var attrMetadata = sender.entities[entityName].Attributes.SingleOrDefault(a => a.LogicalName == orderitem.attribute); if (attrMetadata == null) { throw new ApplicationException($"No metadata for attribute {entityName}.{orderitem.attribute}"); } var odata = new OrderOData { PropertyName = GetPropertyName(attrMetadata), Descending = orderitem.descending }; return(odata); }
// ReSharper disable UnusedParameter.Local // ReSharper disable once UnusedMember.Local private static void ProcessFetchXmlItem(LocalCrmDatabaseOrganizationService service, LinkEntity entityLink, FetchOrderType order) { // Ignore }
private IDataExecutionPlanNode FoldSorts(IDictionary <string, DataSource> dataSources, IQueryExecutionOptions options, IDictionary <string, Type> parameterTypes) { var tryCatch = Source as TryCatchNode; while (tryCatch?.TrySource is TryCatchNode subTry) { tryCatch = subTry; } if (tryCatch != null && tryCatch.TrySource is FetchXmlScan tryFetch && tryFetch.FetchXml.aggregate) { // We're sorting on the results of an aggregate that will try to go as a single FetchXML request first, then to a separate plan // if that fails. Try to fold the sorts in to the aggregate FetchXML first var fetchAggregateSort = new SortNode { Source = tryFetch }; fetchAggregateSort.Sorts.AddRange(Sorts); var sortedFetchResult = fetchAggregateSort.FoldSorts(dataSources, options, parameterTypes); // If we managed to fold any of the sorts in to the FetchXML, do the same for the non-FetchXML version and remove this node if (sortedFetchResult == tryFetch || (sortedFetchResult == fetchAggregateSort && fetchAggregateSort.PresortedCount > 0)) { tryCatch.TrySource = sortedFetchResult; sortedFetchResult.Parent = tryCatch; while (tryCatch != null) { var nonFetchAggregateSort = new SortNode { Source = tryCatch.CatchSource }; nonFetchAggregateSort.Sorts.AddRange(Sorts); var sortedNonFetchResult = nonFetchAggregateSort.FoldSorts(dataSources, options, parameterTypes); tryCatch.CatchSource = sortedNonFetchResult; sortedNonFetchResult.Parent = tryCatch; tryCatch = tryCatch.Parent as TryCatchNode; } return(Source); } } // Allow folding sorts around filters and Compute Scalar (so long as sort is not on a calculated field) var source = Source; var fetchXml = Source as FetchXmlScan; while (source != null && fetchXml == null) { if (source is FilterNode filter) { source = filter.Source; } else if (source is ComputeScalarNode computeScalar) { source = computeScalar.Source; } else { break; } fetchXml = source as FetchXmlScan; } if (fetchXml != null) { if (!dataSources.TryGetValue(fetchXml.DataSource, out var dataSource)) { throw new QueryExecutionException("Missing datasource " + fetchXml.DataSource); } // Remove any existing sorts if (fetchXml.Entity.Items != null) { fetchXml.Entity.Items = fetchXml.Entity.Items.Where(i => !(i is FetchOrderType)).ToArray(); foreach (var linkEntity in fetchXml.Entity.GetLinkEntities().Where(le => le.Items != null)) { linkEntity.Items = linkEntity.Items.Where(i => !(i is FetchOrderType)).ToArray(); } } var fetchSchema = fetchXml.GetSchema(dataSources, parameterTypes); var entity = fetchXml.Entity; var items = entity.Items; foreach (var sortOrder in Sorts) { if (!(sortOrder.Expression is ColumnReferenceExpression sortColRef)) { return(this); } if (!fetchSchema.ContainsColumn(sortColRef.GetColumnName(), out var sortCol)) { return(this); } var parts = sortCol.Split('.'); string entityName; string attrName; if (parts.Length == 2) { entityName = parts[0]; attrName = parts[1]; } else { attrName = parts[0]; entityName = FindEntityWithAttributeAlias(fetchXml, attrName); } var fetchSort = new FetchOrderType { attribute = attrName.ToLowerInvariant(), descending = sortOrder.SortOrder == SortOrder.Descending }; if (fetchXml.FetchXml.aggregate) { fetchSort.alias = fetchSort.attribute; fetchSort.attribute = null; } if (entityName == fetchXml.Alias) { if (items != entity.Items) { return(this); } if (fetchSort.attribute != null) { var meta = dataSource.Metadata[entity.name]; var attribute = meta.Attributes.SingleOrDefault(a => a.LogicalName == fetchSort.attribute && a.AttributeOf == null); // Sorting on a lookup Guid column actually sorts by the associated name field, which isn't what we want if (attribute is LookupAttributeMetadata || attribute is EnumAttributeMetadata || attribute is BooleanAttributeMetadata) { return(this); } // Sorting on multi-select picklist fields isn't supported in FetchXML if (attribute is MultiSelectPicklistAttributeMetadata) { return(this); } // Sorts on the virtual ___name attribute should be applied to the underlying field if (attribute == null && fetchSort.attribute.EndsWith("name") == true) { attribute = meta.Attributes.SingleOrDefault(a => a.LogicalName == fetchSort.attribute.Substring(0, fetchSort.attribute.Length - 4) && a.AttributeOf == null); if (attribute != null) { fetchSort.attribute = attribute.LogicalName; } } if (attribute == null) { return(this); } } entity.AddItem(fetchSort); items = entity.Items; } else { var linkEntity = FetchXmlExtensions.FindLinkEntity(items, entityName); if (linkEntity == null) { return(this); } if (fetchSort.attribute != null) { var meta = dataSource.Metadata[linkEntity.name]; var attribute = meta.Attributes.SingleOrDefault(a => a.LogicalName == fetchSort.attribute && a.AttributeOf == null); // Sorting on a lookup Guid column actually sorts by the associated name field, which isn't what we want if (attribute is LookupAttributeMetadata || attribute is EnumAttributeMetadata || attribute is BooleanAttributeMetadata) { return(this); } // Sorting on multi-select picklist fields isn't supported in FetchXML if (attribute is MultiSelectPicklistAttributeMetadata) { return(this); } // Sorts on the virtual ___name attribute should be applied to the underlying field if (attribute == null && fetchSort.attribute.EndsWith("name") == true) { attribute = meta.Attributes.SingleOrDefault(a => a.LogicalName == fetchSort.attribute.Substring(0, fetchSort.attribute.Length - 4) && a.AttributeOf == null); if (attribute != null) { fetchSort.attribute = attribute.LogicalName; } } if (attribute == null) { return(this); } } // Adding sorts to link-entity forces legacy paging which has a maximum record limit of 50K. // Don't add a sort to a link-entity unless there's also a TOP clause of <= 50K // Doesn't apply to aggregate queries as they can't be paged if (!fetchXml.FetchXml.aggregate) { var top = Parent as TopNode; var offset = Parent as OffsetFetchNode; if (top == null && offset == null) { return(this); } if (top != null) { if (!top.Top.IsConstantValueExpression(null, options, out var topLiteral)) { return(this); } if (Int32.Parse(topLiteral.Value, CultureInfo.InvariantCulture) > 50000) { return(this); } } else if (offset != null) { if (!offset.Offset.IsConstantValueExpression(null, options, out var offsetLiteral) || !offset.Fetch.IsConstantValueExpression(null, options, out var fetchLiteral)) { return(this); } if (Int32.Parse(offsetLiteral.Value, CultureInfo.InvariantCulture) + Int32.Parse(fetchLiteral.Value, CultureInfo.InvariantCulture) > 50000) { return(this); } } } linkEntity.AddItem(fetchSort); items = linkEntity.Items; } PresortedCount++; } return(Source); } // Check if the data is already sorted by any prefix of our sorts var schema = Source.GetSchema(dataSources, parameterTypes); for (var i = 0; i < Sorts.Count && i < schema.SortOrder.Count; i++) { if (!(Sorts[i].Expression is ColumnReferenceExpression col)) { return(this); } if (!schema.ContainsColumn(col.GetColumnName(), out var colName)) { return(this); } if (!schema.SortOrder[i].Equals(colName, StringComparison.OrdinalIgnoreCase)) { return(this); } PresortedCount++; } if (PresortedCount == Sorts.Count) { return(Source); } return(this); }