1 //---------------------------------------------------------------------
2 // <copyright file="Span.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 //---------------------------------------------------------------------
10 namespace System.Data.Objects
12 using System.Collections.Generic;
13 using System.Data.Common.Internal;
14 using System.Diagnostics;
18 /// A collection of paths to determine which entities are spanned into a query.
20 internal sealed class Span
22 private List<SpanPath> _spanList;
23 private string _cacheKey;
27 _spanList = new List<SpanPath>();
31 /// The list of paths that should be spanned into the query
33 internal List<SpanPath> SpanList
35 get { return _spanList; }
39 /// Checks whether relationship span needs to be performed. Currently this is only when the query is
40 /// not using MergeOption.NoTracking.
42 /// <param name="mergeOption"></param>
43 /// <returns>True if the query needs a relationship span rewrite</returns>
44 internal static bool RequiresRelationshipSpan(MergeOption mergeOption)
46 return (mergeOption != MergeOption.NoTracking);
50 /// Includes the specified span path in the specified span instance and returns the updated span instance.
51 /// If <paramref name="spanToIncludeIn"/> is null, a new span instance is constructed and returned that contains
52 /// the specified include path.
54 /// <param name="spanToIncludeIn">The span instance to which the include path should be added. May be null</param>
55 /// <param name="pathToInclude">The include path to add</param>
56 /// <returns>A non-null span instance that contains the specified include path in addition to any paths ut already contained</returns>
57 internal static Span IncludeIn(Span spanToIncludeIn, string pathToInclude)
59 if (null == spanToIncludeIn)
61 spanToIncludeIn = new Span();
64 spanToIncludeIn.Include(pathToInclude);
65 return spanToIncludeIn;
69 /// Returns a span instance that is the union of the two specified span instances.
70 /// If <paramref name="span1"/> and <paramref name="span2"/> are both <c>null</c>,
71 /// then <c>null</c> is returned.
72 /// If <paramref name="span1"/> or <paramref name="span2"/> is null, but the remaining argument is non-null,
73 /// then the non-null argument is returned.
74 /// If neither <paramref name="span1"/> nor <paramref name="span2"/> are null, a new span instance is returned
75 /// that contains the merged span paths from both.
77 /// <param name="span1">The first span instance from which to include span paths; may be <c>null</c></param>
78 /// <param name="span2">The second span instance from which to include span paths; may be <c>null</c></param>
79 /// <returns>A span instance representing the union of the two arguments; may be <c>null</c> if both arguments are null</returns>
80 internal static Span CopyUnion(Span span1, Span span2)
92 Span retSpan = span1.Clone();
93 foreach (SpanPath path in span2.SpanList)
95 retSpan.AddSpanPath(path);
101 internal string GetCacheKey()
103 if (null == _cacheKey)
105 if (_spanList.Count > 0)
107 // If there is only a single Include path with a single property,
108 // then simply use the property name as the cache key rather than
109 // creating any new strings.
110 if (_spanList.Count == 1 &&
111 _spanList[0].Navigations.Count == 1)
113 _cacheKey = _spanList[0].Navigations[0];
117 StringBuilder keyBuilder = new StringBuilder();
118 for (int pathIdx = 0; pathIdx < _spanList.Count; pathIdx++)
122 keyBuilder.Append(";");
125 SpanPath thisPath = _spanList[pathIdx];
126 keyBuilder.Append(thisPath.Navigations[0]);
127 for (int propIdx = 1; propIdx < thisPath.Navigations.Count; propIdx++)
129 keyBuilder.Append(".");
130 keyBuilder.Append(thisPath.Navigations[propIdx]);
134 _cacheKey = keyBuilder.ToString();
143 /// Adds a path to span into the query.
145 /// <param name="path">The path to span</param>
146 public void Include(string path)
148 EntityUtil.CheckStringArgument(path, "path");
149 if (path.Trim().Length == 0)
151 throw new ArgumentException(System.Data.Entity.Strings.ObjectQuery_Span_WhiteSpacePath, "path");
154 SpanPath spanPath = new SpanPath(ParsePath(path));
155 AddSpanPath(spanPath);
160 /// Creates a new Span with the same SpanPaths as this Span
162 /// <returns></returns>
163 internal Span Clone()
165 Span newSpan = new Span();
166 newSpan.SpanList.AddRange(_spanList);
167 newSpan._cacheKey = this._cacheKey;
173 /// Adds the path if it does not already exist
175 /// <param name="spanPath"></param>
176 internal void AddSpanPath(SpanPath spanPath)
178 if (ValidateSpanPath(spanPath))
180 RemoveExistingSubPaths(spanPath);
181 _spanList.Add(spanPath);
186 /// Returns true if the path can be added
188 /// <param name="spanPath"></param>
189 private bool ValidateSpanPath(SpanPath spanPath)
192 // Check for dupliacte entries
193 for (int i = 0; i < _spanList.Count; i++)
195 // make sure spanPath is not a sub-path of anything already in the list
196 if (spanPath.IsSubPath(_spanList[i]))
204 private void RemoveExistingSubPaths(SpanPath spanPath)
206 List<SpanPath> toDelete = new List<SpanPath>();
207 for (int i = 0; i < _spanList.Count; i++)
209 // make sure spanPath is not a sub-path of anything already in the list
210 if (_spanList[i].IsSubPath(spanPath))
212 toDelete.Add(_spanList[i]);
216 foreach (SpanPath path in toDelete)
218 _spanList.Remove(path);
223 /// Storage for a span path
224 /// Currently this includes the list of navigation properties
226 internal class SpanPath
228 public readonly List<string> Navigations;
230 public SpanPath(List<string> navigations)
232 Navigations = navigations;
235 public bool IsSubPath(SpanPath rhs)
237 // this is a subpath of rhs if it has fewer paths, and all the path element values are equal
238 if (Navigations.Count > rhs.Navigations.Count)
243 for (int i = 0; i < Navigations.Count; i++)
245 if (!Navigations[i].Equals(rhs.Navigations[i], StringComparison.OrdinalIgnoreCase))
255 private static List<string> ParsePath(string path)
257 List<string> navigations = MultipartIdentifier.ParseMultipartIdentifier(path, "[", "]", '.');
259 for (int i = navigations.Count - 1; i >= 0; i--)
261 if (navigations[i] == null)
263 navigations.RemoveAt(i);
265 else if (navigations[i].Length == 0)
267 throw EntityUtil.SpanPathSyntaxError();
271 Debug.Assert(navigations.Count > 0, "Empty path found");