/// <summary> /// Applies an <see cref="ExpressionExpand"/> on the <see cref="EntityQuery{T}"/> to determine what related tables to /// include in the result, any tables touched by the <see cref="ExpressionSelect"/> will have it overriding the /// <see cref="ExpressionExpand"/>. /// </summary> public EntityQuery <T> Expand(ExpressionExpand expand) { var clone = Clone(); clone._expand = expand; return(clone); }
/// <summary> /// Backbone for <see cref="CountAsync(int, QueryContext, CancellationToken)"/>, /// <see cref="ToListAsync(QueryContext, CancellationToken)"/> and /// <see cref="ToListAndCountAsync(int, QueryContext, CancellationToken)"/>. /// </summary> private async Task <EntityOutput <T> > ToListAndCountInnerAsync(bool includeResult, bool includeCount, int maxCount, QueryContext ctx, CancellationToken cancellation) { var queryArgs = await _factory(cancellation); var connString = queryArgs.ConnectionString; var sources = queryArgs.Sources; var loader = queryArgs.Loader; var userId = ctx.UserId; var userToday = ctx.UserToday; var resultDesc = TypeDescriptor.Get <T>(); _orderby ??= (IsEntityWithKey() ? ExpressionOrderBy.Parse("Id desc") : throw new InvalidOperationException($"Query<{resultDesc.Type.Name}> was executed without an orderby clause")); // Prepare all the query parameters ExpressionSelect selectExp = _select; ExpressionExpand expandExp = _expand; ExpressionOrderBy orderbyExp = _orderby; ExpressionFilter filterExp = _filter; // To prevent SQL injection ValidatePathsAndProperties(selectExp, expandExp, filterExp, orderbyExp, resultDesc); // ------------------------ Step #1 // Segment the paths of select and expand along the one-to-many relationships, each one-to-many relationship will // result in a new internal query for the child collection with the original query as its principal query var segments = new Dictionary <ArraySegment <string>, EntityQueryInternal>(new PathEqualityComparer()); // Helper method for creating a an internal query, will be used later in both the select and the expand loops EntityQueryInternal MakeQueryInternal(ArraySegment <string> previousFullPath, ArraySegment <string> subPath, TypeDescriptor desc) { EntityQueryInternal principalQuery = previousFullPath == null ? null : segments[previousFullPath]; ArraySegment <string> pathToCollectionPropertyInPrincipal = previousFullPath == null ? null : subPath; if (principalQuery != null && desc.KeyType == KeyType.None) { // Programmer mistake throw new InvalidOperationException($"[Bug] Type {desc.Name} has no Id property, yet it is used as a navigation collection on another entity"); } string foreignKeyToPrincipalQuery = null; bool isAncestorExpand = false; if (principalQuery != null) { // This loop retrieves the entity descriptor that has the collection property TypeDescriptor collectionPropertyEntity = principalQuery.ResultDescriptor; int i = 0; for (; i < pathToCollectionPropertyInPrincipal.Count - 1; i++) { var step = pathToCollectionPropertyInPrincipal[i]; collectionPropertyEntity = collectionPropertyEntity.NavigationProperty(step).TypeDescriptor; } // Get the collection/Parent property string propertyName = pathToCollectionPropertyInPrincipal[i]; var property = collectionPropertyEntity.Property(propertyName); if (property is NavigationPropertyDescriptor navProperty && navProperty.IsParent) { foreignKeyToPrincipalQuery = "ParentId"; isAncestorExpand = true; } else if (property is CollectionPropertyDescriptor collProperty) { // Must be a collection then foreignKeyToPrincipalQuery = collProperty.ForeignKeyName; } else { throw new InvalidOperationException($"Bug: Segment along a property {property.Name} on type {collectionPropertyEntity.Name} That is neither a collection nor a parent"); } } if (isAncestorExpand) { // the path to parent entity is the path above minus the "Parent" var pathToParentEntity = new ArraySegment <string>( array: pathToCollectionPropertyInPrincipal.Array, offset: 0, count: pathToCollectionPropertyInPrincipal.Count - 1); // Adding this causes the principal query to always include ParentId in the select clause principalQuery.PathsToParentEntitiesWithExpandedAncestors.Add(pathToParentEntity); } // This is the orderby of related queries, and the default orderby of the root query var defaultOrderBy = ExpressionOrderBy.Parse( desc.HasProperty("Index") ? "Index" : desc.HasProperty("SortKey") ? "SortKey" : "Id"); // Prepare the flat query and return it var flatQuery = new EntityQueryInternal { PrincipalQuery = principalQuery, IsAncestorExpand = isAncestorExpand, PathToCollectionPropertyInPrincipal = pathToCollectionPropertyInPrincipal, ForeignKeyToPrincipalQuery = foreignKeyToPrincipalQuery, ResultDescriptor = desc, OrderBy = defaultOrderBy }; return(flatQuery); }
/// <summary> /// A version of <see cref="Expand(ExpressionExpand)"/> that accepts a string. /// </summary> public EntityQuery <T> Expand(string expand) { return(Expand(ExpressionExpand.Parse(expand))); }