Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Objects / ObjectParameterCollection.cs
1 //---------------------------------------------------------------------
2 // <copyright file="ObjectParameterCollection.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner Microsoft
7 // @backupowner Microsoft
8 //---------------------------------------------------------------------
9
10 namespace System.Data.Objects
11 {
12     using System;
13     using System.Collections;
14     using System.Collections.Generic;
15     using System.Data;
16     using System.Data.Entity;
17     using System.Data.Metadata.Edm;
18     using System.Text;
19
20     /// <summary>
21     ///   This class represents a collection of query parameters at the object layer.
22     /// </summary>
23     public sealed class ObjectParameterCollection : ICollection<ObjectParameter>
24     {
25         // Note: There are NO public constructors for this class - it is for internal
26         // ObjectQuery<T> use only, but must be public so that an instance thereof can be
27         // a public property on ObjectQuery<T>.
28     
29         #region Internal Constructors
30
31         // ---------------------
32         // Internal Constructors
33         // ---------------------
34
35         #region ObjectParameterCollection (ClrPerspective)
36
37         /// <summary>
38         ///   This internal constructor creates a new query parameter collection and
39         ///   initializes the internal parameter storage.
40         /// </summary>
41         internal ObjectParameterCollection(ClrPerspective perspective)
42         {
43             EntityUtil.CheckArgumentNull(perspective, "perspective");
44  
45             // The perspective is required to do type-checking on parameters as they
46             // are added to the collection.
47             this._perspective = perspective;
48
49             // Create a new list to store the parameters.
50             _parameters = new List<ObjectParameter>();
51         }
52
53         #endregion
54
55         #endregion
56
57         #region Private Fields
58
59         // --------------
60         // Private Fields
61         // --------------
62
63         /// <summary>
64         ///     Can parameters be added or removed from this collection?
65         /// </summary>
66         private bool _locked;
67
68         /// <summary>
69         ///   The internal storage for the query parameters in the collection.
70         /// </summary>
71         private List<ObjectParameter> _parameters;
72
73         /// <summary>
74         ///   A CLR perspective necessary to do type-checking on parameters as they 
75         ///   are added to the collection.
76         /// </summary>
77         private ClrPerspective _perspective;
78
79         /// <summary>
80         /// A string that can be used to represent the current state of this parameter collection in an ObjectQuery cache key.
81         /// </summary>
82         private string _cacheKey;
83
84         #endregion
85
86         #region Public Properties
87
88         // -----------------
89         // Public Properties
90         // -----------------
91
92         /// <summary>
93         ///   The number of parameters currently in the collection.
94         /// </summary>
95         public int Count
96         {
97             get
98             {
99                 return this._parameters.Count;
100             }
101         }
102
103         /// <summary>
104         ///   This collection is read-write - parameters may be added, removed
105         ///   and [somewhat] modified at will (value only) - provided that the
106         ///   implementation the collection belongs to has not locked its parameters
107         ///   because it's command definition has been prepared.
108         /// </summary>
109         bool ICollection<ObjectParameter>.IsReadOnly
110         {
111             get
112             {
113                 return (this._locked);
114             }
115         }
116
117         #endregion
118
119         #region Public Indexers
120
121         // ---------------
122         // Public Indexers
123         // ---------------
124
125         /// <summary>
126         ///   This indexer allows callers to retrieve parameters by name. If no
127         ///   parameter by the given name exists, an exception is thrown. For
128         ///   safe existence-checking, use the Contains method instead.
129         /// </summary>
130         /// <param name="name">
131         ///   The name of the parameter to find.
132         /// </param>
133         /// <returns>
134         ///   The parameter object with the specified name.
135         /// </returns>
136         /// <exception cref="ArgumentOutOfRangeException">
137         ///   If no parameter with the specified name is found in the collection.
138         /// </exception>
139         public ObjectParameter this[string name]
140         {
141             get
142             {
143                 int index = this.IndexOf(name);
144
145                 if (index == -1)
146                 {
147                     throw EntityUtil.ArgumentOutOfRange(System.Data.Entity.Strings.ObjectParameterCollection_ParameterNameNotFound(name), "name");
148                 }
149
150                 return this._parameters[index];
151             }
152         }
153
154         #endregion
155
156         #region Public Methods
157
158         // --------------
159         // Public Methods
160         // --------------
161
162         #region Add
163
164         /// <summary>
165         ///   This method adds the specified parameter object to the collection. If
166         ///   the parameter object already exists in the collection, an exception is 
167         ///   thrown.
168         /// </summary>
169         /// <param name="parameter">
170         ///   The parameter object to add to the collection.
171         /// </param>
172         /// <returns></returns>
173         /// <exception cref="ArgumentNullException">
174         ///   If the value of the parameter argument is null.
175         /// </exception>
176         /// <exception cref="ArgumentException">
177         ///   If the parameter argument already exists in the collection. This 
178         ///   behavior differs from that of most collections which allow duplicate
179         ///   entries.
180         /// </exception>
181         /// <exception cref="ArgumentException">
182         ///   If another parameter with the same name as the parameter argument
183         ///   already exists in the collection. Note that the lookup is case-
184         ///   insensitive. This behavior differs from that of most collections,  
185         ///   and is more like that of a Dictionary.
186         /// </exception>
187         /// <exception cref="ArgumentOutOfRangeException">
188         ///   If the type of the specified parameter is invalid. 
189         /// </exception>
190         public void Add (ObjectParameter parameter)
191         {
192             EntityUtil.CheckArgumentNull(parameter, "parameter");
193             CheckUnlocked();
194
195             if (this.Contains(parameter))
196             {
197                 throw EntityUtil.Argument(System.Data.Entity.Strings.ObjectParameterCollection_ParameterAlreadyExists(parameter.Name), "parameter");
198             }
199
200             if (this.Contains(parameter.Name))
201             {
202                 throw EntityUtil.Argument(System.Data.Entity.Strings.ObjectParameterCollection_DuplicateParameterName(parameter.Name), "parameter");
203             }
204
205             if (!parameter.ValidateParameterType(this._perspective))
206             {
207                 throw EntityUtil.ArgumentOutOfRange(System.Data.Entity.Strings.ObjectParameter_InvalidParameterType(parameter.ParameterType.FullName), "parameter");
208             }
209
210             this._parameters.Add(parameter);
211             this._cacheKey = null;
212         }
213
214         #endregion
215
216         #region Clear
217
218         /// <summary>
219         ///   This method empties the entire parameter collection.
220         /// </summary>
221         /// <returns></returns>
222         public void Clear()
223         {
224             CheckUnlocked();
225             this._parameters.Clear();
226             this._cacheKey = null;
227         }
228
229         #endregion
230
231         #region Contains (ObjectParameter)
232
233         /// <summary>
234         ///   This methods checks for the existence of a given parameter object in the
235         ///   collection by reference.
236         /// </summary>
237         /// <param name="parameter">
238         ///   The parameter object to look for in the collection.
239         /// </param>
240         /// <returns>
241         ///   True if the parameter object was found in the collection, false otherwise.
242         ///   Note that this is a reference-based lookup, which means that if the para-
243         ///   meter argument has the same name as a parameter object in the collection,
244         ///   this method will only return true if it's the same object. 
245         /// </returns>
246         /// <exception cref="ArgumentNullException">
247         ///   If the value of the parameter argument is null.
248         /// </exception>
249         public bool Contains (ObjectParameter parameter)
250         {
251             EntityUtil.CheckArgumentNull(parameter, "parameter");
252             
253             return this._parameters.Contains(parameter);
254         }
255
256         #endregion
257
258         #region Contains (string)
259
260         /// <summary>
261         ///   This method checks for the existence of a given parameter in the collection
262         ///   by name. 
263         /// </summary>
264         /// <param name="name">
265         ///   The name of the parameter to look for in the collection.
266         /// </param>
267         /// <returns>
268         ///   True if a parameter with the specified name was found in the collection,
269         ///   false otherwise. Note that the lookup is case-insensitive.
270         /// </returns>
271         /// <exception cref="ArgumentNullException">
272         ///   If the value of the parameter argument is null.
273         /// </exception>
274         public bool Contains (string name)
275         {
276             EntityUtil.CheckArgumentNull(name, "name");
277             
278             if (this.IndexOf(name) != -1)
279             {
280                 return true;
281             }
282
283             return false;
284         }
285
286         #endregion
287
288         #region CopyTo
289
290         /// <summary>
291         ///   This method allows the parameters in the collection to be copied into a
292         ///   supplied array, beginning at the specified index therein.
293         /// </summary>
294         /// <param name="array">
295         ///   The array into which to copy the parameters.
296         /// </param>
297         /// <param name="index">
298         ///   The index in the array at which to start copying the parameters.
299         /// </param>
300         /// <returns></returns>
301         public void CopyTo (ObjectParameter[] array, int index)
302         {
303             this._parameters.CopyTo(array, index);
304         }
305
306         #endregion
307
308         #region Remove
309
310         /// <summary>
311         ///   This method removes an instance of a parameter from the collection by 
312         ///   reference if it exists in the collection.  To remove a parameter by name, 
313         ///   first use the Contains(name) method or this[name] indexer to retrieve
314         ///   the parameter instance, then remove it using this method.
315         /// </summary>
316         /// <param name="parameter">
317         ///   The parameter object to remove from the collection.
318         /// </param>
319         /// <returns>
320         ///   True if the parameter object was found and removed from the collection,
321         ///   false otherwise. Note that this is a reference-based lookup, which means 
322         ///   that if the parameter argument has the same name as a parameter object 
323         ///   in the collection, this method will remove it only if it's the same object.  
324         /// </returns>
325         /// <exception cref="ArgumentNullException">
326         ///   If the value of the parameter argument is null.
327         /// </exception>
328         public bool Remove (ObjectParameter parameter)
329         {
330             EntityUtil.CheckArgumentNull(parameter, "parameter");
331             CheckUnlocked();
332             
333             bool removed = this._parameters.Remove(parameter);
334
335             // If the specified parameter was found in the collection and removed, 
336             // clear out the cached string representation of this parameter collection
337             // so that the next call to GetCacheKey (if any) will regenerate it based on
338             // the new state of this collection.
339             if (removed)
340             {
341                 this._cacheKey = null;
342             }
343
344             return removed;
345         }
346
347         #endregion
348   
349         #region GetEnumerator
350
351         /// <summary>
352         ///   These methods return enumerator instances, which allow the collection to
353         ///   be iterated through and traversed.
354         /// </summary>
355         IEnumerator<ObjectParameter> IEnumerable<ObjectParameter>.GetEnumerator() 
356         {
357             return ((System.Collections.Generic.ICollection<ObjectParameter>)this._parameters).GetEnumerator();
358         }
359         
360         IEnumerator IEnumerable.GetEnumerator() 
361         {
362             return ((System.Collections.ICollection)this._parameters).GetEnumerator();
363         }
364
365         #endregion
366
367         #endregion
368
369         #region Internal Methods
370
371         // ---------------
372         // Internal Methods
373         // ---------------
374
375         /// <summary>
376         /// Retrieves a string that may be used to represent this parameter collection in an ObjectQuery cache key.
377         /// If this collection has not changed since the last call to this method, the same string instance is returned.
378         /// Note that this string is used by various ObjectQueryImplementations to version the parameter collection.
379         /// </summary>
380         /// <returns>A string that may be used to represent this parameter collection in an ObjectQuery cache key.</returns>
381         internal string GetCacheKey()
382         {
383             if (null == this._cacheKey)
384             {
385                 if(this._parameters.Count > 0)
386                 {
387                     // Future Enhancement: If the separate branch for a single parameter does not have a measurable perf advantage, remove it.
388                     if (1 == this._parameters.Count)
389                     {
390                         // if its one parameter only, there is no need to use stringbuilder
391                         ObjectParameter theParam = this._parameters[0];
392                         this._cacheKey = "@@1" + theParam.Name + ":" + theParam.ParameterType.FullName;
393                     }
394                     else
395                     {
396                         // Future Enhancement: Investigate whether precalculating the required size of the string builder is a better time/space tradeoff.
397                         StringBuilder keyBuilder = new StringBuilder(this._parameters.Count * 20);
398                         keyBuilder.Append("@@");
399                         keyBuilder.Append(this._parameters.Count);
400                         for (int idx = 0; idx < this._parameters.Count; idx++)
401                         {
402                             //
403                             // 
404
405                             if (idx > 0)
406                             {
407                                 keyBuilder.Append(";");
408                             }
409
410                             ObjectParameter thisParam = this._parameters[idx];
411                             keyBuilder.Append(thisParam.Name);
412                             keyBuilder.Append(":");
413                             keyBuilder.Append(thisParam.ParameterType.FullName);
414                         }
415
416                         this._cacheKey = keyBuilder.ToString();
417                     }
418                 }
419             }
420
421             return this._cacheKey;
422         }
423
424         /// <summary>
425         /// Locks or unlocks this parameter collection, allowing its contents to be added to, removed from, or cleared.
426         /// Calling this method consecutively with the same value has no effect but does not throw an exception.
427         /// </summary>
428         /// <param name="isReadOnly">If <c>true</c>, this parameter collection is now locked; otherwise it is unlocked</param>
429         internal void SetReadOnly(bool isReadOnly) { this._locked = isReadOnly; }
430
431         /// <summary>
432         /// Creates a new copy of the specified parameter collection containing copies of its element <see cref="ObjectParameter"/>s.
433         /// If the specified argument is <c>null</c>, then <c>null</c> is returned.
434         /// </summary>
435         /// <param name="copyParams">The parameter collection to copy</param>
436         /// <returns>The new collection containing copies of <paramref name="copyParams"/> parameters, if <paramref name="copyParams"/> is non-null; otherwise <c>null</c>.</returns>
437         internal static ObjectParameterCollection DeepCopy(ObjectParameterCollection copyParams)
438         {
439             if (null == copyParams)
440             {
441                 return null;
442             }
443
444             ObjectParameterCollection retParams = new ObjectParameterCollection(copyParams._perspective);
445             foreach (ObjectParameter param in copyParams)
446             {
447                 retParams.Add(param.ShallowCopy());
448             }
449
450             return retParams;
451         }
452
453         #endregion
454
455         #region Private Methods
456
457         // ---------------
458         // Private Methods
459         // ---------------
460
461         /// <summary>
462         ///   This private method checks for the existence of a given parameter object
463         ///   by name by iterating through the list and comparing each parameter name 
464         ///   to the specified name. This is a case-insensitive lookup.
465         /// </summary>
466         private int IndexOf (string name)
467         {
468             int index = 0;
469
470             foreach (ObjectParameter parameter in this._parameters)
471             {
472                 if (0 == String.Compare(name, parameter.Name, StringComparison.OrdinalIgnoreCase))
473                 {
474                     return index;
475                 }
476
477                 index++;
478             }
479
480             return -1;
481         }
482
483         /// <summary>
484         ///     This method successfully returns only if the parameter collection is not considered 'locked';
485         ///     otherwise an <see cref="InvalidOperationException"/> is thrown.
486         /// </summary>
487         private void CheckUnlocked()
488         {
489             if (this._locked)
490             {
491                 throw EntityUtil.InvalidOperation(Strings.ObjectParameterCollection_ParametersLocked);
492             }
493         }
494
495         #endregion
496     }
497 }