/// <summary> /// Prepares the join tree /// </summary> private JoinTree PrepareJoin() { // construct the join tree var allPaths = new List <string[]>(); if (Select != null) { allPaths.AddRange(Select.Select(e => e.Path)); } if (Filter != null) { allPaths.AddRange(Filter.Select(e => e.Path)); } // This will represent the mapping from paths to symbols var joinTree = JoinTree.Make(ResultType, allPaths); return(joinTree); }
/// <summary> /// Create the <see cref="JoinTree"/> from the paths in all the arguments /// </summary> private JoinTree PrepareJoin(ArraySegment <string>?pathToCollection = null) { // construct the join tree var allPaths = new List <string[]>(); if (Select != null) { allPaths.AddRange(Select.Select(e => e.Path)); } if (Expand != null) { allPaths.AddRange(Expand.Select(e => e.Path)); } if (Filter != null) { allPaths.AddRange(Filter.Select(e => e.Path)); } if (OrderBy != null) { allPaths.AddRange(OrderBy.Select(e => e.Path)); } if (pathToCollection != null) { var pathToCollectionEntity = new ArraySegment <string>( pathToCollection.Value.Array, pathToCollection.Value.Offset, pathToCollection.Value.Count - 1); allPaths.Add(pathToCollectionEntity.ToArray()); } // This will represent the mapping from paths to symbols return(JoinTree.Make(ResultType, allPaths)); }
/// <summary> /// Prepares the SELECT statement and the column map, using the <see cref="Select"/> argument /// </summary> private SqlSelectClause PrepareSelect(JoinTree joinTree) { var selects = new HashSet <(string Symbol, string PropName)>(); var columns = new List <(string Symbol, ArraySegment <string> Path, string PropName)>(); void AddSelect(string symbol, ArraySegment <string> path, string propName) { // NULL happens when there is a select that has been segmented from the middle // and the first section of the segment no longer terminates with a simple property // propName = propName ?? "Id"; if (propName == null) { return; } if (selects.Add((symbol, propName))) { columns.Add((symbol, path, propName)); } } // Any path step that is touched by a select (which has a property) ignores the expand, the joinTree below // allows us to efficiently check if any particular step is touched by a select JoinTree overridingSelectTree = Select == null ? null : JoinTree.Make(ResultType, Select.Select(e => e.Path)); // Overriding select paths // Optimization: remember the joins that have been selected and don't select them again HashSet <JoinTree> selectedJoins = new HashSet <JoinTree>(); // For every expanded entity that has not been tainted by a select argument, we add all its properties to the list of selects Expand ??= ExpandExpression.Empty; foreach (var expand in Expand.Union(ExpandExpression.RootSingleton)) { string[] path = expand.Path; for (int i = 0; i <= path.Length; i++) { var subpath = new ArraySegment <string>(path, 0, i); var selectMatch = overridingSelectTree?[subpath]; if (selectMatch == null) // This expand is not overridden by a select { var join = joinTree[subpath]; if (join == null) { // Developer mistake throw new InvalidOperationException($"The path '{string.Join('/', subpath)}' was not found in the joinTree"); } else if (selectedJoins.Contains(join)) { continue; } else { selectedJoins.Add(join); } foreach (var prop in join.Type.GetMappedProperties()) { AddSelect(join.Symbol, subpath, prop.Name); } } } } if (Select != null) { foreach (var select in Select) { // Add the property string[] path = select.Path; { var join = joinTree[path]; var propName = select.Property; // Can be null AddSelect(join.Symbol, path, propName); } // In this loop we ensure all levels to the selected properties // have their Ids and Foreign Keys added to the select collection for (int i = 0; i <= path.Length; i++) { var subpath = new ArraySegment <string>(path, 0, i); var join = joinTree[subpath]; if (join == null) { // Developer mistake throw new InvalidOperationException($"The path '{string.Join('/', subpath)}' was not found in the joinTree"); } else if (selectedJoins.Contains(join)) { // All properties were added earlier in an expand continue; } else { selectedJoins.Add(join); } // The Id is ALWAYS required in every EntityWithKey if (join.Type.IsSubclassOf(typeof(EntityWithKey))) { AddSelect(join.Symbol, subpath, "Id"); } // Add all the foreign keys to the next level down foreach (var nextJoin in join.Values) { AddSelect(join.Symbol, subpath, nextJoin.ForeignKeyName); } } } } // If the foreign key to the principal query is specified, then always include that // otherwise there will be no way to link the collection to the principal query once we load the data if (!string.IsNullOrWhiteSpace(ForeignKeyToPrincipalQuery)) { var path = new string[0]; AddSelect(joinTree.Symbol, path, ForeignKeyToPrincipalQuery); } // Deals with trees foreach (var path in PathsToParentEntitiesWithExpandedAncestors) { var join = joinTree[path]; AddSelect(join.Symbol, path, "ParentId"); } if (IsAncestorExpand) { var path = new string[0]; AddSelect(joinTree.Symbol, path, "ParentId"); } // Change the hash set to a list so that the order is well defined return(new SqlSelectClause(columns)); }