1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
|
//---------------------------------------------------------------------
// <copyright file="ObjectFullSpanRewriter.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @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
{
/// <summary>
/// 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.
/// </summary>
private class SpanPathInfo
{
internal SpanPathInfo(EntityType declaringType)
{
this.DeclaringType = declaringType;
}
/// <summary>
/// The effective Entity type of this node in the tree
/// </summary>
internal EntityType DeclaringType;
/// <summary>
/// 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
/// </summary>
internal Dictionary<NavigationProperty, SpanPathInfo> Children;
}
/// <summary>
/// Maintains a reference to the SpanPathInfo tree node representing the
/// current position in the 'Include' path that is currently being expanded.
/// </summary>
private Stack<SpanPathInfo> _currentSpanPath = new Stack<SpanPathInfo>();
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<T> that produced
// the Command Tree that is being rewritten. This could be either
// literally 'T' or Collection<T>.
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);
}
/// <summary>
/// Populates the Include span tree with appropriate branches for the Include path
/// represented by the specified list of navigation property names.
/// </summary>
/// <param name="parentInfo">The root SpanPathInfo</param>
/// <param name="navPropNames">A list of navigation property names that describes a single Include span path</param>
private void AddSpanPath(SpanPathInfo parentInfo, List<string> navPropNames)
{
ConvertSpanPath(parentInfo, navPropNames, 0);
}
private void ConvertSpanPath(SpanPathInfo parentInfo, List<string> 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<NavigationProperty, SpanPathInfo>();
}
// 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);
}
}
/// <summary>
/// Retrieves the Entity (result or element) type produced by a Navigation Property.
/// </summary>
/// <param name="navProp">The navigation property</param>
/// <returns>
/// 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.
/// </returns>
private static EntityType EntityTypeFromResultType(NavigationProperty navProp)
{
EntityType retType = null;
TryGetEntityType(navProp.TypeUsage, out retType);
// Currently, navigation properties may only return an Entity or Collection<Entity> result
Debug.Assert(retType != null, "Navigation property has non-Entity and non-Entity collection result type?");
return retType;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="resultType">The TypeUsage that provides the EdmType to examine</param>
/// <param name="entityType">The referenced Entity (element) type, if present.</param>
/// <returns>
/// <c>true</c> if the specified <paramref name="resultType"/> is an Entity type or a
/// collection type with an Entity element type; otherwise <c>false</c>.
/// </returns>
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;
}
/// <summary>
/// Utility method to retrieve the 'To' AssociationEndMember of a NavigationProperty
/// </summary>
/// <param name="property">The navigation property</param>
/// <returns>The AssociationEndMember that is the target of the navigation operation represented by the NavigationProperty</returns>
private AssociationEndMember GetNavigationPropertyTargetEnd(NavigationProperty property)
{
AssociationType relationship = this.Metadata.GetItem<AssociationType>(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<NavigationProperty, SpanPathInfo> 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<string, DbExpression>(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;
}
}
}
|