File: ObjectFullSpanRewriter.cs

package info (click to toggle)
mono 6.12.0.199%2Bdfsg-6
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 1,296,836 kB
  • sloc: cs: 11,181,803; xml: 2,850,076; ansic: 699,709; cpp: 123,344; perl: 59,361; javascript: 30,841; asm: 21,853; makefile: 20,405; sh: 15,009; python: 4,839; pascal: 925; sql: 859; sed: 16; php: 1
file content (263 lines) | stat: -rw-r--r-- 14,237 bytes parent folder | download | duplicates (8)
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;
        }
    }
}