//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner Microsoft // @backupowner Microsoft //--------------------------------------------------------------------- namespace System.Data.Objects.Internal { using System.Collections.Generic; using System.Data.Common.CommandTrees; using System.Data.Common.CommandTrees.ExpressionBuilder; using System.Data.Common.Utils; using System.Data.Metadata.Edm; using System.Diagnostics; internal class ObjectFullSpanRewriter : ObjectSpanRewriter { /// /// Represents a node in the 'Include' navigation property tree /// built from the list of SpanPaths on the Span object with which /// the FullSpanRewriter is constructed. /// private class SpanPathInfo { internal SpanPathInfo(EntityType declaringType) { this.DeclaringType = declaringType; } /// /// The effective Entity type of this node in the tree /// internal EntityType DeclaringType; /// /// Describes the navigation properties that should be retrieved /// from this node in the tree and the Include sub-paths that extend /// from each of those navigation properties /// internal Dictionary Children; } /// /// Maintains a reference to the SpanPathInfo tree node representing the /// current position in the 'Include' path that is currently being expanded. /// private Stack _currentSpanPath = new Stack(); internal ObjectFullSpanRewriter(DbCommandTree tree, DbExpression toRewrite, Span span, AliasGenerator aliasGenerator) : base(tree, toRewrite, aliasGenerator) { Debug.Assert(span != null, "Span cannot be null"); Debug.Assert(span.SpanList.Count > 0, "At least one span path is required"); // Retrieve the effective 'T' of the ObjectQuery that produced // the Command Tree that is being rewritten. This could be either // literally 'T' or Collection. EntityType entityType = null; if (!TryGetEntityType(this.Query.ResultType, out entityType)) { // If the result type of the query is neither an Entity type nor a collection // type with an Entity element type, then full Span is currently not allowed. throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.ObjectQuery_Span_IncludeRequiresEntityOrEntityCollection); } // Construct the SpanPathInfo navigation property tree using the // list of Include Span paths from the Span object: // Create a SpanPathInfo instance that represents the root of the tree // and takes its Entity type from the Entity type of the result type of the query. SpanPathInfo spanRoot = new SpanPathInfo(entityType); // Populate the tree of navigation properties based on the navigation property names // in the Span paths from the Span object. Commonly rooted span paths are merged, so // that paths of "Customer.Order" and "Customer.Address", for example, will share a // common SpanPathInfo for "Customer" in the Children collection of the root SpanPathInfo, // and that SpanPathInfo will contain one child for "Order" and another for "Address". foreach (Span.SpanPath path in span.SpanList) { AddSpanPath(spanRoot, path.Navigations); } // The 'current' span path is initialized to the root of the Include span tree _currentSpanPath.Push(spanRoot); } /// /// Populates the Include span tree with appropriate branches for the Include path /// represented by the specified list of navigation property names. /// /// The root SpanPathInfo /// A list of navigation property names that describes a single Include span path private void AddSpanPath(SpanPathInfo parentInfo, List navPropNames) { ConvertSpanPath(parentInfo, navPropNames, 0); } private void ConvertSpanPath(SpanPathInfo parentInfo, List navPropNames, int pos) { // Attempt to retrieve the next navigation property from the current entity type // using the name of the current navigation property in the Include path. NavigationProperty nextNavProp = null; if (!parentInfo.DeclaringType.NavigationProperties.TryGetValue(navPropNames[pos], true, out nextNavProp)) { // The navigation property name is not valid for this Entity type throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.ObjectQuery_Span_NoNavProp(parentInfo.DeclaringType.FullName, navPropNames[pos])); } // The navigation property was retrieved, an entry for it must be ensured in the Children // collection of the parent SpanPathInfo instance. // If the parent's Children collection does not exist then instantiate it now: if (null == parentInfo.Children) { parentInfo.Children = new Dictionary(); } // If a sub-path that begins with the current navigation property name was already // encountered, then a SpanPathInfo for this navigation property may already exist // in the Children dictionary... SpanPathInfo nextChild = null; if (!parentInfo.Children.TryGetValue(nextNavProp, out nextChild)) { // ... otherwise, create a new SpanPathInfo instance that this navigation // property maps to and ensure its presence in the Children dictionary. nextChild = new SpanPathInfo(EntityTypeFromResultType(nextNavProp)); parentInfo.Children[nextNavProp] = nextChild; } // If this navigation property is not the end of the span path then // increment the position and recursively call ConvertSpanPath, specifying // the (retrieved or newly-created) SpanPathInfo of this navigation property // as the new 'parent' info. if (pos < navPropNames.Count - 1) { ConvertSpanPath(nextChild, navPropNames, pos + 1); } } /// /// Retrieves the Entity (result or element) type produced by a Navigation Property. /// /// The navigation property /// /// The Entity type produced by the navigation property. /// This may be the immediate result type (if the result is at most one) /// or the element type of the result type, otherwise. /// private static EntityType EntityTypeFromResultType(NavigationProperty navProp) { EntityType retType = null; TryGetEntityType(navProp.TypeUsage, out retType); // Currently, navigation properties may only return an Entity or Collection result Debug.Assert(retType != null, "Navigation property has non-Entity and non-Entity collection result type?"); return retType; } /// /// Retrieves the Entity (result or element) type referenced by the specified TypeUsage, if /// its EdmType is an Entity type or a collection type with an Entity element type. /// /// The TypeUsage that provides the EdmType to examine /// The referenced Entity (element) type, if present. /// /// true if the specified is an Entity type or a /// collection type with an Entity element type; otherwise false. /// private static bool TryGetEntityType(TypeUsage resultType, out EntityType entityType) { // If the result type is an Entity, then simply use that type. if (BuiltInTypeKind.EntityType == resultType.EdmType.BuiltInTypeKind) { entityType = (EntityType)resultType.EdmType; return true; } else if (BuiltInTypeKind.CollectionType == resultType.EdmType.BuiltInTypeKind) { // If the result type of the query is a collection, attempt to extract // the element type of the collection and determine if it is an Entity type. EdmType elementType = ((CollectionType)resultType.EdmType).TypeUsage.EdmType; if (BuiltInTypeKind.EntityType == elementType.BuiltInTypeKind) { entityType = (EntityType)elementType; return true; } } entityType = null; return false; } /// /// Utility method to retrieve the 'To' AssociationEndMember of a NavigationProperty /// /// The navigation property /// The AssociationEndMember that is the target of the navigation operation represented by the NavigationProperty private AssociationEndMember GetNavigationPropertyTargetEnd(NavigationProperty property) { AssociationType relationship = this.Metadata.GetItem(property.RelationshipType.FullName, DataSpace.CSpace); Debug.Assert(relationship.AssociationEndMembers.Contains(property.ToEndMember.Name), "Association does not declare member referenced by Navigation property?"); return relationship.AssociationEndMembers[property.ToEndMember.Name]; } internal override SpanTrackingInfo CreateEntitySpanTrackingInfo(DbExpression expression, EntityType entityType) { SpanTrackingInfo tracking = new SpanTrackingInfo(); SpanPathInfo currentInfo = _currentSpanPath.Peek(); if (currentInfo.Children != null) { // The current SpanPathInfo instance on the top of the span path stack indicates // which navigation properties should be retrieved from this Entity-typed expression // and also specifies (in the form of child SpanPathInfo instances) which sub-paths // must be expanded for each of those navigation properties. // The SpanPathInfo instance may be the root instance or a SpanPathInfo that represents a sub-path. int idx = 1; // SpanRoot is always the first (zeroth) column, full- and relationship-span columns follow. foreach (KeyValuePair nextInfo in currentInfo.Children) { // If the tracking information was not initialized yet, do so now. if (null == tracking.ColumnDefinitions) { tracking = InitializeTrackingInfo(this.RelationshipSpan); } // Create a property expression that retrieves the specified navigation property from the Entity-typed expression. // Note that the expression is cloned since it may be used as the instance of multiple property expressions. DbExpression columnDef = expression.Property(nextInfo.Key); // Rewrite the result of the navigation property. This is required for two reasons: // 1. To continue spanning the current Include path. // 2. To apply relationship span to the Entity or EntityCollection produced by the navigation property, if necessary. // Consider an Include path of "Order" for a query that returns OrderLines - the Include'd Orders should have // their associated Customer relationship spanned. // Note that this will recursively call this method with the Entity type of the result of the // navigation property, which will in turn call loop through the sub-paths of this navigation // property and adjust the stack to track which Include path is being expanded and which // element of that path is considered 'current'. _currentSpanPath.Push(nextInfo.Value); columnDef = this.Rewrite(columnDef); _currentSpanPath.Pop(); // Add a new column to the tracked columns using the rewritten column definition tracking.ColumnDefinitions.Add(new KeyValuePair(tracking.ColumnNames.Next(), columnDef)); AssociationEndMember targetEnd = GetNavigationPropertyTargetEnd(nextInfo.Key); tracking.SpannedColumns[idx] = targetEnd; // If full span and relationship span are both required, a relationship span may be rendered // redundant by an already added full span. Therefore the association ends that have been expanded // as part of full span are tracked using a dictionary. if (this.RelationshipSpan) { tracking.FullSpannedEnds[targetEnd] = true; } idx++; } } return tracking; } } }