84c60eb7e870df2a79fb61016896a9fe5a48d46a
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / EntityKey.cs
1 //---------------------------------------------------------------------
2 // <copyright file="EntityKey.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner       Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9
10 using System.Collections;
11 using System.Collections.Generic;
12 using System.ComponentModel;
13 using System.Data.Common;
14 using System.Data.Common.CommandTrees;
15 using System.Data.Common.CommandTrees.ExpressionBuilder;
16 using System.Data.Common.Utils;
17 using System.Data.Metadata.Edm;
18 using System.Diagnostics;
19 using System.Runtime.Serialization;
20 using Edm = System.Data.Metadata.Edm;
21
22 namespace System.Data
23 {
24     /// <summary>
25     /// An identifier for an entity.
26     /// </summary>
27     [DebuggerDisplay("{ConcatKeyValue()}")]
28     [Serializable]
29     [DataContract(IsReference = true)]
30     public sealed class EntityKey : IEquatable<EntityKey>
31     {
32         // The implementation of EntityKey is optimized for the following common cases:
33         //      1) Keys constructed internally rather by the user - in particular, keys 
34         //         created by the bridge on the round-trip from query.
35         //      2) Single-valued (as opposed to composite) keys.
36         // We accomplish this by maintaining two variables, at most one of which is non-null.
37         // The first is of type object and in the case of a singleton key, is set to the
38         // single key value.  The second is an object array and in the case of 
39         // a composite key, is set to the list of key values.  If both variables are null,
40         // the EntityKey is a temporary key.  Note that the key field names
41         // are not stored - for composite keys, the values are stored in the order in which
42         // metadata reports the corresponding key members.
43
44         // The following 5 fields are serialized.  Adding or removing a serialized field is considered
45         // a breaking change.  This includes changing the field type or field name of existing
46         // serialized fields. If you need to make this kind of change, it may be possible, but it
47         // will require some custom serialization/deserialization code.
48         private string _entitySetName;
49         private string _entityContainerName;
50         private object _singletonKeyValue;      // non-null for singleton keys
51         private object[] _compositeKeyValues;   // non-null for composite keys
52         private string[] _keyNames;             // key names that correspond to the key values
53         private bool _isLocked;                 // determines if this key is lock from writing
54
55         // Determines whether the key includes a byte[].
56         // Not serialized for backwards compatibility.
57         // This value is computed along with the _hashCode, which is also not serialized.
58         [NonSerialized]
59         private bool _containsByteArray;
60
61         [NonSerialized]
62         private EntityKeyMember[] _deserializedMembers;
63
64         // The hash code is not serialized since it can be computed differently on the deserialized system.
65         [NonSerialized]
66         private int _hashCode;                  // computed as needed
67
68
69         // Names for constant EntityKeys
70         private const string s_NoEntitySetKey = "NoEntitySetKey.NoEntitySetKey";
71         private const string s_EntityNotValidKey = "EntityNotValidKey.EntityNotValidKey";
72
73         /// <summary>
74         /// A singleton EntityKey by which a read-only entity is identified.
75         /// </summary>
76         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")]     // Justification: these are internal so they cannot be modified publically
77         public static readonly EntityKey NoEntitySetKey = new EntityKey(s_NoEntitySetKey);
78
79         /// <summary>
80         /// A singleton EntityKey identifying an entity resulted from a failed TREAT.
81         /// </summary>
82         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")]     // Justification: these are internal so they cannot be modified publically
83         public static readonly EntityKey EntityNotValidKey = new EntityKey(s_EntityNotValidKey);
84
85         /// <summary>
86         /// A dictionary of names so that singleton instances of names can be used
87         /// </summary>
88         private static Dictionary<string, string> _nameLookup = new Dictionary<string, string>();
89
90         #region Public Constructors
91
92         /// <summary>
93         /// Constructs an empty EntityKey. For use during XmlSerialization.
94         /// </summary>
95         public EntityKey()
96         {
97             _isLocked = false;
98         }
99
100         /// <summary>
101         /// Constructs an EntityKey with the given key values.
102         /// </summary>
103         /// <param name="qualifiedEntitySetName">The EntitySet name, qualified by the EntityContainer name, of the entity</param>
104         /// <param name="entityKeyValues">The key-value pairs that identify the entity</param>
105         public EntityKey(string qualifiedEntitySetName, IEnumerable<KeyValuePair<string, object>> entityKeyValues)
106         {
107             GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
108             CheckKeyValues(entityKeyValues, out _keyNames, out _singletonKeyValue, out _compositeKeyValues);
109             AssertCorrectState(null, false);
110             _isLocked = true;
111         }
112
113         /// <summary>
114         /// Constructs an EntityKey with the given key values.
115         /// </summary>
116         /// <param name="qualifiedEntitySetName">The EntitySet name, qualified by the EntityContainer name, of the entity</param>
117         /// <param name="entityKeyValues">The key-value pairs that identify the entity</param>
118         public EntityKey(string qualifiedEntitySetName, IEnumerable<EntityKeyMember> entityKeyValues)
119         {
120             GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
121             EntityUtil.CheckArgumentNull(entityKeyValues, "entityKeyValues");
122             CheckKeyValues(new KeyValueReader(entityKeyValues), out _keyNames, out _singletonKeyValue, out _compositeKeyValues);
123             AssertCorrectState(null, false);
124             _isLocked = true;
125         }
126
127         /// <summary>
128         /// Constructs an EntityKey with the given single key name and value.
129         /// </summary>
130         /// <param name="qualifiedEntitySetName">The EntitySet name, qualified by the EntityContainer name, of the entity</param>
131         /// <param name="keyName">The key name that identifies the entity</param>
132         /// <param name="keyValue">The key value that identifies the entity</param>
133         public EntityKey(string qualifiedEntitySetName, string keyName, object keyValue)
134         {
135             GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
136             EntityUtil.CheckStringArgument(keyName, "keyName");
137             EntityUtil.CheckArgumentNull(keyValue, "keyValue");
138
139             _keyNames = new string[1];
140             ValidateName(keyName);
141             _keyNames[0] = keyName;
142             _singletonKeyValue = keyValue;
143
144             AssertCorrectState(null, false);
145             _isLocked = true;
146         }
147
148         #endregion
149
150         #region Internal Constructors
151
152         /// <summary>
153         /// Constructs an EntityKey from an IExtendedDataRecord representing the entity.
154         /// </summary>
155         /// <param name="entitySet">EntitySet of the entity</param>
156         /// <param name="record">an IExtendedDataRecord that represents the entity</param>
157         internal EntityKey(EntitySet entitySet, IExtendedDataRecord record)
158         {
159             Debug.Assert(entitySet != null, "entitySet is null");
160             Debug.Assert(entitySet.Name != null, "entitySet.Name is null");
161             Debug.Assert(entitySet.EntityContainer != null, "entitySet.EntityContainer is null");
162             Debug.Assert(entitySet.EntityContainer.Name != null, "entitySet.EntityContainer.Name is null");
163             Debug.Assert(record != null, "record is null");
164             
165             _entitySetName = entitySet.Name;
166             _entityContainerName = entitySet.EntityContainer.Name;
167
168             GetKeyValues(entitySet, record, out _keyNames, out _singletonKeyValue, out _compositeKeyValues);
169             AssertCorrectState(entitySet, false);
170             _isLocked = true;
171         }
172
173         /// <summary>
174         /// Constructs an EntityKey from an IExtendedDataRecord representing the entity.
175         /// </summary>
176         /// <param name="entitySet">EntitySet of the entity</param>
177         /// <param name="record">an IExtendedDataRecord that represents the entity</param>
178         internal EntityKey(string qualifiedEntitySetName)
179         {
180             GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
181             _isLocked = true;
182         }
183
184         /// <summary>
185         /// Constructs a temporary EntityKey with the given EntitySet.
186         /// Temporary keys do not store key field names
187         /// </summary>
188         /// <param name="entitySet">EntitySet of the entity</param>
189         internal EntityKey(EntitySetBase entitySet)
190         {
191             EntityUtil.CheckArgumentNull(entitySet, "entitySet");
192             Debug.Assert(entitySet.EntityContainer != null, "EntitySet.EntityContainer cannot be null.");
193
194             _entitySetName = entitySet.Name;
195             _entityContainerName = entitySet.EntityContainer.Name;
196
197             AssertCorrectState(entitySet, true);
198             _isLocked = true;
199         }
200
201         /// <summary>
202         /// Constructor optimized for a singleton key.
203         /// SQLBUDT 478655: Performance optimization: Does no integrity checking on the key value.
204         /// SQLBUDT 523554: Performance optimization: Does no validate type of key members.
205         /// </summary>
206         /// <param name="entitySet">EntitySet of the entity</param>
207         /// <param name="singletonKeyValue">The single value that composes the entity's key, assumed to contain the correct type.</param>
208         internal EntityKey(EntitySetBase entitySet, object singletonKeyValue)
209         {
210             Debug.Assert(entitySet != null, "EntitySet cannot be null.");
211             Debug.Assert(entitySet.EntityContainer != null, "EntitySet.EntityContainer cannot be null.");
212             Debug.Assert(singletonKeyValue != null, "Singleton key value cannot be null.");
213             _singletonKeyValue = singletonKeyValue;
214             _entitySetName = entitySet.Name;
215             _entityContainerName = entitySet.EntityContainer.Name;
216             _keyNames = entitySet.ElementType.KeyMemberNames; // using EntitySetBase avoids an (EntityType) cast that EntitySet encoure
217             AssertCorrectState(entitySet, false);
218             _isLocked = true;
219         }
220
221         /// <summary>
222         /// Constructor optimized for a composite key.
223         /// SQLBUDT 478655: Performance optimization: Does no integrity checking on the key values.
224         /// SQLBUDT 523554: Performance optimization: Does no validate type of key members.
225         /// </summary>
226         /// <param name="entitySet">EntitySet of the entity</param>
227         /// <param name="compositeKeyValues">A list of the values (at least 2) that compose the entity's key, assumed to contain correct types.</param>
228         internal EntityKey(EntitySetBase entitySet, object[] compositeKeyValues)
229         {
230             Debug.Assert(entitySet != null, "EntitySet cannot be null.");
231             Debug.Assert(entitySet.EntityContainer != null, "EntitySet.EntityContainer cannot be null.");
232             Debug.Assert(compositeKeyValues != null, "Composite key values cannot be null.");
233             _compositeKeyValues = compositeKeyValues;
234             _entitySetName = entitySet.Name;
235             _entityContainerName = entitySet.EntityContainer.Name;
236             _keyNames = entitySet.ElementType.KeyMemberNames; // using EntitySetBase avoids an (EntityType) cast that EntitySet encoure
237             AssertCorrectState(entitySet, false);
238             _isLocked = true;
239         }
240
241         #endregion
242
243         /// <summary>
244         /// Gets the EntitySet name identifying the entity set that contains the entity.
245         /// </summary>
246         [DataMember]
247         public string EntitySetName
248         {
249             get
250             {
251                 return _entitySetName;
252             }
253             set
254             {
255                 ValidateWritable(_entitySetName);
256                 lock (_nameLookup)
257                 {
258                     _entitySetName = EntityKey.LookupSingletonName(value);
259                 }
260             }
261         }
262
263         /// <summary>
264         /// Gets the EntityContainer name identifying the entity container that contains the entity.
265         /// </summary>
266         [DataMember]
267         public string EntityContainerName
268         {
269             get
270             {
271                 return _entityContainerName;
272             }
273             set
274             {
275                 ValidateWritable(_entityContainerName);
276                 lock (_nameLookup)
277                 {
278                     _entityContainerName = EntityKey.LookupSingletonName(value);
279                 }
280             }
281         }
282
283
284         /// <summary>
285         /// Gets the key values that identify the entity.
286         /// </summary>
287         [DataMember]
288         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Required for this feature")]
289         public EntityKeyMember[] EntityKeyValues
290         {
291             get
292             {
293                 if (!IsTemporary)
294                 {
295                     EntityKeyMember[] keyValues;
296                     if (_singletonKeyValue != null)
297                     {
298                         keyValues = new EntityKeyMember[] { 
299                                 new EntityKeyMember(_keyNames[0], _singletonKeyValue) };
300                     }
301                     else
302                     {
303                         keyValues = new EntityKeyMember[_compositeKeyValues.Length];
304                         for (int i = 0; i < _compositeKeyValues.Length; ++i)
305                         {
306                             keyValues[i] = new EntityKeyMember(_keyNames[i], _compositeKeyValues[i]);
307                         }
308                     }
309                     return keyValues;
310                 }
311                 else
312                 {
313                     return null;
314                 }
315             }
316             set
317             {
318                 ValidateWritable(_keyNames);
319                 if (value != null)
320                 {
321                     if (!CheckKeyValues(new KeyValueReader(value), true, true, out _keyNames, out _singletonKeyValue, out _compositeKeyValues))
322                     {
323                         // If we did not retrieve values from the setter (i.e. encoded settings), we need to keep track of the 
324                         // array instance because the array members will be set next.
325                         _deserializedMembers = value;
326                     }
327                 }
328             }
329         }
330
331         /// <summary>
332         /// Gets a value indicating whether this key is a temporary key.
333         /// </summary>
334         public bool IsTemporary
335         {
336             get
337             {
338                 return (SingletonKeyValue == null) && (CompositeKeyValues == null);
339             }
340         }
341
342         private object SingletonKeyValue
343         {
344             get
345             {
346                 if (RequiresDeserialization)
347                 {
348                     DeserializeMembers();
349                 }
350                 return _singletonKeyValue;
351             }
352         }
353
354         private object[] CompositeKeyValues
355         {
356             get
357             {
358                 if (RequiresDeserialization)
359                 {
360                     DeserializeMembers();
361                 }
362                 return _compositeKeyValues;
363             }
364         }
365
366         /// <summary>
367         /// Gets the entity set for this entity key from the given metadata workspace, by
368         /// entity container name and entity set name.
369         /// </summary>
370         /// <param name="metadataWorkspace">workspace in which to look up the entity set</param>
371         /// <returns>the entity set from the given workspace for this entity key</returns>
372         /// <exception cref="ArgumentException">the entity set could not be located in the workspace</exception>
373         public EntitySet GetEntitySet(MetadataWorkspace metadataWorkspace)
374         {
375             EntityUtil.CheckArgumentNull(metadataWorkspace, "metadataWorkspace");
376             if (String.IsNullOrEmpty(_entityContainerName) || String.IsNullOrEmpty(_entitySetName))
377             {
378                 throw EntityUtil.MissingQualifiedEntitySetName();
379             }
380
381             // GetEntityContainer will throw if it cannot find the container
382
383             // SQLBUDT 479443:  If this entity key was initially created using an entity set 
384             // from a different workspace, look up the entity set in the new workspace.
385             // Metadata will throw an ArgumentException if the entity set could not be found.
386
387             return metadataWorkspace
388                 .GetEntityContainer(_entityContainerName, DataSpace.CSpace)
389                 .GetEntitySetByName(_entitySetName, false);
390         }
391
392         #region Equality/Hashing
393
394         /// <summary>
395         /// Compares this instance to a given key by their values.
396         /// </summary>
397         /// <param name="obj">the key to compare against this instance</param>
398         /// <returns>true if this instance is equal to the given key, and false otherwise</returns>
399         public override bool Equals(object obj)
400         {
401             return InternalEquals(this, obj as EntityKey, compareEntitySets: true);
402         }
403
404         /// <summary>
405         /// Compares this instance to a given key by their values.
406         /// </summary>
407         /// <param name="other">the key to compare against this instance</param>
408         /// <returns>true if this instance is equal to the given key, and false otherwise</returns>
409         public bool Equals(EntityKey other)
410         {
411             return InternalEquals(this, other, compareEntitySets: true);
412         }
413
414         /// <summary>
415         /// Returns a value-based hash code, to allow EntityKey to be used in hash tables.
416         /// </summary>
417         /// <returns>the hash value of this EntityKey</returns>
418         public override int GetHashCode()
419         {
420             int hashCode = _hashCode;
421             if (0 == hashCode)
422             {
423                 _containsByteArray = false;
424
425                 if (RequiresDeserialization)
426                 {
427                     DeserializeMembers();
428                 }
429
430                 if (_entitySetName != null)
431                 {
432                     hashCode = _entitySetName.GetHashCode();
433                 }
434                 if (_entityContainerName != null)
435                 {
436                     hashCode ^= _entityContainerName.GetHashCode();
437                 }
438
439                 // If the key is not temporary, determine a hash code based on the value(s) within the key.
440                 if (null != _singletonKeyValue)
441                 {
442                     hashCode = AddHashValue(hashCode, _singletonKeyValue);
443                 }
444                 else if (null != _compositeKeyValues)
445                 {
446                     for (int i = 0, n = _compositeKeyValues.Length; i < n; i++)
447                     {
448                         hashCode = AddHashValue(hashCode, _compositeKeyValues[i]);
449                     }
450                 }
451                 else
452                 {
453                     // If the key is temporary, use default hash code
454                     hashCode = base.GetHashCode();
455                 }
456
457                 // cache the hash code if we are a locked or fully specified EntityKey
458                 if (_isLocked || (!String.IsNullOrEmpty(_entitySetName) &&
459                                   !String.IsNullOrEmpty(_entityContainerName) &&
460                                   (_singletonKeyValue != null || _compositeKeyValues != null)))
461                 {
462                     _hashCode = hashCode;
463                 }
464             }
465             return hashCode;
466         }
467
468         private int AddHashValue(int hashCode, object keyValue)
469         {
470             byte[] byteArrayValue = keyValue as byte[];
471             if (null != byteArrayValue)
472             {
473                 hashCode ^= ByValueEqualityComparer.ComputeBinaryHashCode(byteArrayValue);
474                 _containsByteArray = true;
475                 return hashCode;
476             }
477             else
478             {
479                 return hashCode ^ keyValue.GetHashCode();
480             }
481         }
482
483         /// <summary>
484         /// Compares two keys by their values.
485         /// </summary>
486         /// <param name="key1">a key to compare</param>
487         /// <param name="key2">a key to compare</param>
488         /// <returns>true if the two keys are equal, false otherwise</returns>
489         public static bool operator ==(EntityKey key1, EntityKey key2)
490         {
491 #if DEBUG
492             if (((object)NoEntitySetKey == (object)key1) || ((object)EntityNotValidKey == (object)key1) ||
493                 ((object)NoEntitySetKey == (object)key2) || ((object)EntityNotValidKey == (object)key1)
494                 // || (null==(object)key1) || (null==(object)key2)) //To check for internal use of null==key
495                 )
496             {
497                 Debug.Assert(typeof(EntityKey).Assembly != System.Reflection.Assembly.GetCallingAssembly(), "When comparing an EntityKey to one of the predefined types (EntityKey.NoEntitySetKey or EntityKey.EntityNotValidKey), use Object.ReferenceEquals()");
498             }
499 #endif
500             return InternalEquals(key1, key2, compareEntitySets: true);
501         }
502
503         /// <summary>
504         /// Compares two keys by their values.
505         /// </summary>
506         /// <param name="key1">a key to compare</param>
507         /// <param name="key2">a key to compare</param>
508         /// <returns>true if the two keys are not equal, false otherwise</returns>
509         public static bool operator !=(EntityKey key1, EntityKey key2)
510         {
511 #if DEBUG
512             if (((object)NoEntitySetKey == (object)key1) || ((object)EntityNotValidKey == (object)key1) ||
513                 ((object)NoEntitySetKey == (object)key2) || ((object)EntityNotValidKey == (object)key1))
514             // || (null==(object)key1) || (null==(object)key2)) //To check for internal use of null==key
515             {
516                 Debug.Assert(typeof(EntityKey).Assembly != System.Reflection.Assembly.GetCallingAssembly(), "When comparing an EntityKey to one of the predefined types (EntityKey.NoEntitySetKey or EntityKey.EntityNotValidKey), use Object.ReferenceEquals()");
517             }
518 #endif
519             return !InternalEquals(key1, key2, compareEntitySets: true);
520         }
521
522         /// <summary>
523         /// Internal function to compare two keys by their values.
524         /// </summary>
525         /// <param name="key1">a key to compare</param>
526         /// <param name="key2">a key to compare</param>
527         /// <param name="compareEntitySets">Entity sets are not significant for conceptual null keys</param>
528         /// <returns>true if the two keys are equal, false otherwise</returns>
529         internal static bool InternalEquals(EntityKey key1, EntityKey key2, bool compareEntitySets)
530         {
531             // If both are null or refer to the same object, they're equal.
532             if (object.ReferenceEquals(key1, key2))
533             {
534                 return true;
535             }
536
537             // If exactly one is null (avoid calling EntityKey == operator overload), they're not equal.
538             if (object.ReferenceEquals(key1, null) || object.ReferenceEquals(key2, null))
539             {
540                 return false;
541             }
542
543             // If the hash codes differ, the keys are not equal.  Note that 
544             // a key's hash code is cached after being computed for the first time, 
545             // so this check will only incur the cost of computing a hash code 
546             // at most once for a given key.
547
548             // The primary caller is Dictionary<EntityKey,ObjectStateEntry>
549             // at which point Equals is only called after HashCode was determined to be equal
550             if ((key1.GetHashCode() != key2.GetHashCode() && compareEntitySets) ||
551                 key1._containsByteArray != key2._containsByteArray)
552             {
553                 return false;
554             }
555
556             if (null != key1._singletonKeyValue)
557             {
558                 if (key1._containsByteArray)
559                 {
560                     // Compare the single value (if the second is null, false should be returned)
561                     if (null == key2._singletonKeyValue)
562                     {
563                         return false;
564                     }
565
566                     // they are both byte[] because they have the same _containsByteArray value of true, and only a single value
567                     if (!ByValueEqualityComparer.CompareBinaryValues((byte[])key1._singletonKeyValue, (byte[])key2._singletonKeyValue))
568                     {
569                         return false;
570                     }
571                 }
572                 else
573                 {
574                     // not a byte array
575                     if (!key1._singletonKeyValue.Equals(key2._singletonKeyValue))
576                     {
577                         return false;
578                     }
579                 }
580
581                 // Check key names
582                 if (!String.Equals(key1._keyNames[0], key2._keyNames[0]))
583                 {
584                     return false;
585                 }
586             }
587             else
588             {
589                 // If either key is temporary, they're not equal.  This is because
590                 // temporary keys are compared by CLR reference, and we've already
591                 // checked reference equality.
592                 // If the first key is a composite key and the second one isn't, they're not equal.
593                 if (null != key1._compositeKeyValues && null != key2._compositeKeyValues && key1._compositeKeyValues.Length == key2._compositeKeyValues.Length)
594                 {
595                     if (key1._containsByteArray)
596                     {
597                         if (!CompositeValuesWithBinaryEqual(key1, key2))
598                         {
599                             return false;
600                         }
601                     }
602                     else
603                     {
604                         if (!CompositeValuesEqual(key1, key2))
605                         {
606                             return false;
607                         }
608                     }
609                 }
610                 else
611                 {
612                     return false;
613                 }
614             }
615
616             if (compareEntitySets)
617             {
618                 // Check metadata.
619                 if (!String.Equals(key1._entitySetName, key2._entitySetName) ||
620                     !String.Equals(key1._entityContainerName, key2._entityContainerName))
621                 {
622                     return false;
623                 }
624             }
625
626             return true;
627         }
628
629         internal static bool CompositeValuesWithBinaryEqual(EntityKey key1, EntityKey key2)
630         {
631             for (int i = 0; i < key1._compositeKeyValues.Length; ++i)
632             {
633                 if (key1._keyNames[i].Equals(key2._keyNames[i]))
634                 {
635                     if (!ByValueEqualityComparer.Default.Equals(key1._compositeKeyValues[i], key2._compositeKeyValues[i]))
636                     {
637                         return false;
638                     }
639                 }
640                 // Key names might not be in the same order so try a slower approach that matches
641                 // key names between the keys.
642                 else if (!ValuesWithBinaryEqual(key1._keyNames[i], key1._compositeKeyValues[i], key2))
643                 {
644                     return false;
645                 }
646             }
647             return true;
648         }
649
650         private static bool ValuesWithBinaryEqual(string keyName, object keyValue, EntityKey key2)
651         {
652             for (int i = 0; i < key2._keyNames.Length; i++)
653             {
654                 if (String.Equals(keyName, key2._keyNames[i]))
655                 {
656                     return ByValueEqualityComparer.Default.Equals(keyValue, key2._compositeKeyValues[i]);
657                 }
658             }
659             return false;
660         }
661
662         private static bool CompositeValuesEqual(EntityKey key1, EntityKey key2)
663         {
664             for (int i = 0; i < key1._compositeKeyValues.Length; ++i)
665             {
666                 if (key1._keyNames[i].Equals(key2._keyNames[i]))
667                 {
668                     if (!Object.Equals(key1._compositeKeyValues[i], key2._compositeKeyValues[i]))
669                     {
670                         return false;
671                     }
672                 }
673                 // Key names might not be in the same order so try a slower approach that matches
674                 // key names between the keys.
675                 else if (!ValuesEqual(key1._keyNames[i], key1._compositeKeyValues[i], key2))
676                 {
677                     return false;
678                 }
679             }
680             return true;
681         }
682
683         private static bool ValuesEqual(string keyName, object keyValue, EntityKey key2)
684         {
685             for (int i = 0; i < key2._keyNames.Length; i++)
686             {
687                 if (String.Equals(keyName, key2._keyNames[i]))
688                 {
689                     return Object.Equals(keyValue, key2._compositeKeyValues[i]);
690                 }
691             }
692             return false;
693         }
694
695         #endregion
696
697
698         /// <summary>
699         /// Returns an array of string/<see cref="DbExpression"/> pairs, one for each key value in this EntityKey,
700         /// where the string is the key member name and the DbExpression is the value in this EntityKey
701         /// for that key member, represented as a <see cref="DbConstantExpression"/> with the same result
702         /// type as the key member.
703         /// </summary>
704         /// <param name="entitySet">The entity set to which this EntityKey refers; used to verify that this key has the required key members</param>
705         /// <returns>The name -> expression mappings for the key member values represented by this EntityKey</returns>
706         internal KeyValuePair<string, DbExpression>[] GetKeyValueExpressions(EntitySet entitySet)
707         {
708             Debug.Assert(!IsTemporary, "GetKeyValueExpressions doesn't make sense for temporary keys - they have no values.");
709             Debug.Assert(entitySet != null, "GetEntitySet should not return null.");
710             Debug.Assert(entitySet.Name == _entitySetName, "EntitySet returned from GetEntitySet has incorrect name.");
711             int numKeyMembers = 0;
712             if (!IsTemporary)
713             {
714                 if (_singletonKeyValue != null)
715                 {
716                     numKeyMembers = 1;
717                 }
718                 else
719                 {
720                     numKeyMembers = _compositeKeyValues.Length;
721                 }
722             }
723             if (((EntitySetBase)entitySet).ElementType.KeyMembers.Count != numKeyMembers)
724             {
725                 // If we found an entity set by name that's a different CLR reference 
726                 // than the one contained by this EntityKey, the two entity sets could
727                 // be incompatible.  The only error case we need to handle here is the
728                 // one where the number of key members differs; other error cases
729                 // will be handled by the command tree builder methods.
730
731                 // 
732
733
734                 throw EntityUtil.EntitySetDoesNotMatch("metadataWorkspace", TypeHelpers.GetFullName(entitySet));
735             }
736
737             // Iterate over the internal collection of string->object
738             // key value pairs and create a list of string->constant
739             // expression key value pairs.
740             KeyValuePair<string, DbExpression>[] keyColumns;
741             if (_singletonKeyValue != null)
742             {
743                 EdmMember singletonKeyMember = ((EntitySetBase)entitySet).ElementType.KeyMembers[0];
744                 Debug.Assert(singletonKeyMember != null, "Metadata for singleton key member shouldn't be null.");
745                 keyColumns =
746                     new[] { DbExpressionBuilder.Constant(Helper.GetModelTypeUsage(singletonKeyMember), _singletonKeyValue)
747                             .As(singletonKeyMember.Name) };
748
749             }
750             else
751             {
752                 keyColumns = new KeyValuePair<string, DbExpression>[_compositeKeyValues.Length];
753                 for (int i = 0; i < _compositeKeyValues.Length; ++i)
754                 {
755                     Debug.Assert(_compositeKeyValues[i] != null, "Values within key-value pairs cannot be null.");
756
757                     EdmMember keyMember = ((EntitySetBase)entitySet).ElementType.KeyMembers[i];
758                     Debug.Assert(keyMember != null, "Metadata for key members shouldn't be null.");
759                     keyColumns[i] = DbExpressionBuilder.Constant(Helper.GetModelTypeUsage(keyMember), _compositeKeyValues[i]).As(keyMember.Name);
760                 }
761             }
762
763             return keyColumns;
764         }
765
766         /// <summary>
767         /// Returns a string representation of this EntityKey, for use in debugging.
768         /// Note that the returned string contains potentially sensitive information
769         /// (i.e., key values), and thus shouldn't be publicly exposed.
770         /// </summary>
771         internal string ConcatKeyValue()
772         {
773             System.Text.StringBuilder builder = new System.Text.StringBuilder();
774             builder.Append("EntitySet=").Append(_entitySetName);
775             if (!IsTemporary)
776             {
777                 foreach (EntityKeyMember pair in EntityKeyValues)
778                 {
779                     builder.Append(';');
780                     builder.Append(pair.Key).Append("=").Append(pair.Value);
781                 }
782
783             }
784             return builder.ToString();
785         }
786
787         /// <summary>
788         /// Returns the appropriate value for the given key name. 
789         /// </summary>
790         internal object FindValueByName(string keyName)
791         {
792             Debug.Assert(!IsTemporary, "FindValueByName should not be called for temporary keys.");
793             if (SingletonKeyValue != null)
794             {
795                 Debug.Assert(_keyNames[0] == keyName, "For a singleton key, the given keyName must match.");
796                 return _singletonKeyValue;
797             }
798             else
799             {
800                 object[] compositeKeyValues = CompositeKeyValues;
801                 for (int i = 0; i < compositeKeyValues.Length; i++)
802                 {
803                     if (keyName == _keyNames[i])
804                     {
805                         return compositeKeyValues[i];
806                     }
807                 }
808                 throw EntityUtil.ArgumentOutOfRange("keyName");
809             }
810         }
811
812         internal static void GetEntitySetName(string qualifiedEntitySetName, out  string entitySet, out string container)
813         {
814             entitySet = null;
815             container = null;
816             EntityUtil.CheckStringArgument(qualifiedEntitySetName, "qualifiedEntitySetName");
817
818             string[] result = qualifiedEntitySetName.Split('.');
819             if (result.Length != 2)
820             {
821                 throw EntityUtil.InvalidQualifiedEntitySetName();
822             }
823
824             container = result[0];
825             entitySet = result[1];
826
827             // both parts must be non-empty
828             if (container == null || container.Length == 0 ||
829                 entitySet == null || entitySet.Length == 0)
830             {
831                 throw EntityUtil.InvalidQualifiedEntitySetName();
832             }
833
834             ValidateName(container);
835             ValidateName(entitySet);
836         }
837
838         internal static void ValidateName(string name)
839         {
840             if (!System.Data.EntityModel.SchemaObjectModel.Utils.ValidUndottedName(name))
841             {
842                 throw EntityUtil.EntityKeyInvalidName(name);
843             }
844         }
845
846         #region Key Value Assignment and Validation
847
848         private static bool CheckKeyValues(IEnumerable<KeyValuePair<string, object>> entityKeyValues,
849             out string[] keyNames, out object singletonKeyValue, out object[] compositeKeyValues)
850         {
851             return CheckKeyValues(entityKeyValues, false, false, out keyNames, out singletonKeyValue, out compositeKeyValues);
852         }
853
854         private static bool CheckKeyValues(IEnumerable<KeyValuePair<string, object>> entityKeyValues, bool allowNullKeys, bool tokenizeStrings,
855             out string[] keyNames, out object singletonKeyValue, out object[] compositeKeyValues)
856         {
857             EntityUtil.CheckArgumentNull(entityKeyValues, "entityKeyValues");
858
859             int numExpectedKeyValues;
860             int numActualKeyValues = 0;
861
862             keyNames = null;
863             singletonKeyValue = null;
864             compositeKeyValues = null;
865
866             // Determine if we're a single or composite key.
867             foreach (KeyValuePair<string, object> value in entityKeyValues)
868             {
869                 numActualKeyValues++;
870             }
871
872             numExpectedKeyValues = numActualKeyValues;
873             if (numExpectedKeyValues == 0)
874             {
875                 if (!allowNullKeys)
876                 {
877                     throw EntityUtil.EntityKeyMustHaveValues("entityKeyValues");
878                 }
879             }
880             else
881             {
882                 keyNames = new string[numExpectedKeyValues];
883
884                 if (numExpectedKeyValues == 1)
885                 {
886                     lock (_nameLookup)
887                     {
888                         foreach (KeyValuePair<string, object> keyValuePair in entityKeyValues)
889                         {
890                             if (EntityUtil.IsNull(keyValuePair.Value) || String.IsNullOrEmpty(keyValuePair.Key))
891                             {
892                                 throw EntityUtil.NoNullsAllowedInKeyValuePairs("entityKeyValues");
893                             }
894                             ValidateName(keyValuePair.Key);
895                             keyNames[0] = tokenizeStrings ? EntityKey.LookupSingletonName(keyValuePair.Key) : keyValuePair.Key;
896                             singletonKeyValue = keyValuePair.Value;
897                         }
898                     }
899                 }
900                 else
901                 {
902                     compositeKeyValues = new object[numExpectedKeyValues];
903
904                     int i = 0;
905                     lock (_nameLookup)
906                     {
907                         foreach (KeyValuePair<string, object> keyValuePair in entityKeyValues)
908                         {
909                             if (EntityUtil.IsNull(keyValuePair.Value) || String.IsNullOrEmpty(keyValuePair.Key))
910                             {
911                                 throw EntityUtil.NoNullsAllowedInKeyValuePairs("entityKeyValues");
912                             }
913                             Debug.Assert(null == keyNames[i], "shouldn't have a name yet");
914                             ValidateName(keyValuePair.Key);
915                             keyNames[i] = tokenizeStrings ? EntityKey.LookupSingletonName(keyValuePair.Key) : keyValuePair.Key;
916                             compositeKeyValues[i] = keyValuePair.Value;
917                             i++;
918                         }
919                     }
920                 }
921             }
922             return numExpectedKeyValues > 0;
923         }
924
925         /// <summary>
926         /// Validates the record parameter passed to the EntityKey constructor, 
927         /// and converts the data into the form required by EntityKey.  For singleton keys, 
928         /// this is a single object.  For composite keys, this is an object array.
929         /// </summary>
930         /// <param name="entitySet">the entity set metadata object which this key refers to</param>
931         /// <param name="record">the parameter to validate</param>
932         /// <param name="numExpectedKeyValues">the number of expected key-value pairs</param>
933         /// <param name="argumentName">the name of the argument to use in exception messages</param>
934         /// <param name="workspace">MetadataWorkspace used to resolve and validate enum keys.</param>
935         /// <returns>the validated value(s) (for a composite key, an object array is returned)</returns>
936         private static void GetKeyValues(EntitySet entitySet, IExtendedDataRecord record, 
937             out string[] keyNames, out object singletonKeyValue, out object[] compositeKeyValues)
938         {
939             singletonKeyValue = null;
940             compositeKeyValues = null;
941
942             int numExpectedKeyValues = ((EntitySetBase)entitySet).ElementType.KeyMembers.Count;
943             keyNames = ((EntitySetBase)entitySet).ElementType.KeyMemberNames;
944
945             EntityType entityType = record.DataRecordInfo.RecordType.EdmType as EntityType;
946             Debug.Assert(entityType != null, "Data record must be an entity.");
947
948             // assert the type contained by this entity set matches the type contained by the data record
949             Debug.Assert(entitySet != null && entitySet.ElementType.IsAssignableFrom(entityType), "Entity types do not match");
950             Debug.Assert(numExpectedKeyValues > 0, "Should be expecting a positive number of key-values.");
951
952             if (numExpectedKeyValues == 1)
953             {
954                 // Optimize for a singleton key.
955
956                 EdmMember member = entityType.KeyMembers[0];
957                 singletonKeyValue = record[member.Name];
958                 if (EntityUtil.IsNull(singletonKeyValue))
959                 {
960                     throw EntityUtil.NoNullsAllowedInKeyValuePairs("record");
961                 }
962             }
963             else
964             {
965                 compositeKeyValues = new object[numExpectedKeyValues];
966                 // grab each key-field from the data record
967                 for (int i = 0; i < numExpectedKeyValues; ++i)
968                 {
969                     EdmMember member = entityType.KeyMembers[i];
970                     compositeKeyValues[i] = record[member.Name];
971                     if (EntityUtil.IsNull(compositeKeyValues[i]))
972                     {
973                         throw EntityUtil.NoNullsAllowedInKeyValuePairs("record");
974                     }
975                 }
976             }
977         }
978
979         /// <summary>
980         /// Verify that the types of the objects passed in to be used as keys actually match the types from the model.
981         /// This error is also caught when the entity is materialized and when the key value is set, at which time it
982         /// also throws ThrowSetInvalidValue().
983         /// SQLBUDT 513838. This error is possible and should be caught at run time, not in an assertion.
984         /// </summary>
985         /// <param name="workspace">MetadataWorkspace used to resolve and validate types of enum keys.</param>
986         /// <param name="entitySet">The EntitySet to validate against</param>
987         internal void ValidateEntityKey(MetadataWorkspace workspace, EntitySet entitySet)
988         {
989             ValidateEntityKey(workspace, entitySet, false, null);
990         }
991         /// <summary>
992         /// Verify that the types of the objects passed in to be used as keys actually match the types from the model.
993         /// This error is also caught when the entity is materialized and when the key value is set, at which time it
994         /// also throws ThrowSetInvalidValue().
995         /// SQLBUDT 513838. This error is possible and should be caught at run time, not in an assertion.
996         /// </summary>
997         /// <param name="workspace">MetadataWorkspace used to resolve and validate types of enum keys.</param>
998         /// <param name="entitySet">The EntitySet to validate against</param>
999         /// <param name="isArgumentException">Wether to throw ArgumentException or InvalidOperationException.</param>
1000         /// <param name="argumentName">Name of the argument in case of ArgumentException.</param>
1001         internal void ValidateEntityKey(MetadataWorkspace workspace, EntitySet entitySet, bool isArgumentException, string argumentName)
1002         {
1003             if (entitySet != null)
1004             {
1005                 ReadOnlyMetadataCollection<EdmMember> keyMembers = ((EntitySetBase)entitySet).ElementType.KeyMembers;
1006                 if (_singletonKeyValue != null)
1007                 {
1008                     // 1. Validate number of keys
1009                     if (keyMembers.Count != 1)
1010                     {
1011                         if (isArgumentException)
1012                         {
1013                             throw EntityUtil.IncorrectNumberOfKeyValuePairs(argumentName, entitySet.ElementType.FullName, keyMembers.Count, 1);
1014                         }
1015                         else
1016                         {
1017                             throw EntityUtil.IncorrectNumberOfKeyValuePairsInvalidOperation(entitySet.ElementType.FullName, keyMembers.Count, 1);
1018                         }
1019                     }
1020
1021                     // 2. Validate type of key values
1022                     ValidateTypeOfKeyValue(workspace, keyMembers[0], _singletonKeyValue, isArgumentException, argumentName);
1023
1024                     // 3. Validate key names
1025                     if (_keyNames[0] != keyMembers[0].Name)
1026                     {
1027                         if (isArgumentException)
1028                         {
1029                             throw EntityUtil.MissingKeyValue(argumentName, keyMembers[0].Name, entitySet.ElementType.FullName);
1030                         }
1031                         else
1032                         {
1033                             throw EntityUtil.MissingKeyValueInvalidOperation(keyMembers[0].Name, entitySet.ElementType.FullName);
1034                         }
1035                     }
1036                 }
1037                 else if (null != _compositeKeyValues)
1038                 {
1039                     // 1. Validate number of keys
1040                     if (keyMembers.Count != _compositeKeyValues.Length)
1041                     {
1042                         if (isArgumentException)
1043                         {
1044                             throw EntityUtil.IncorrectNumberOfKeyValuePairs(argumentName, entitySet.ElementType.FullName, keyMembers.Count, _compositeKeyValues.Length);
1045                         }
1046                         else
1047                         {
1048                             throw EntityUtil.IncorrectNumberOfKeyValuePairsInvalidOperation(entitySet.ElementType.FullName, keyMembers.Count, _compositeKeyValues.Length);
1049                         }
1050                     }
1051
1052                     for (int i = 0; i < _compositeKeyValues.Length; ++i)
1053                     {
1054                         EdmMember keyField = ((EntitySetBase)entitySet).ElementType.KeyMembers[i];
1055                         bool foundMember = false;
1056                         for (int j = 0; j < _compositeKeyValues.Length; ++j)
1057                         {
1058                             if (keyField.Name == _keyNames[j])
1059                             {
1060                                 // 2. Validate type of key values
1061                                 ValidateTypeOfKeyValue(workspace, keyField, _compositeKeyValues[j], isArgumentException, argumentName);
1062
1063                                 foundMember = true;
1064                                 break;
1065                             }
1066                         }
1067                         // 3. Validate Key Name (if we found it or not)
1068                         if (!foundMember)
1069                         {
1070                             if (isArgumentException)
1071                             {
1072                                 throw EntityUtil.MissingKeyValue(argumentName, keyField.Name, entitySet.ElementType.FullName);
1073                             }
1074                             else
1075                             {
1076                                 throw EntityUtil.MissingKeyValueInvalidOperation(keyField.Name, entitySet.ElementType.FullName);
1077                             }
1078                         }
1079                     }
1080                 }
1081             }
1082         }
1083
1084         /// <summary>
1085         /// Validates whether type of the key matches the type of the key value.
1086         /// </summary>
1087         /// <param name="workspace">MetadataWorkspace used to resolve and validate types of enum keys.</param>
1088         /// <param name="keyMember">Edm key member.</param>
1089         /// <param name="keyValue">The value of the key.</param>
1090         /// <param name="isArgumentException">Whether to throw ArgumentException or InvalidOperation exception if validation fails.</param>
1091         /// <param name="argumentName">Name of the argument to be used for ArgumentExceptions.</param>
1092         private static void ValidateTypeOfKeyValue(MetadataWorkspace workspace, EdmMember keyMember, object keyValue, bool isArgumentException, string argumentName)
1093         {
1094             Debug.Assert(workspace != null, "workspace != null");
1095             Debug.Assert(keyMember != null, "keyMember != null");
1096             Debug.Assert(keyValue != null, "keyValue != null");
1097             Debug.Assert(Helper.IsScalarType(keyMember.TypeUsage.EdmType), "key member must be of a scalar type");
1098
1099             EdmType keyMemberEdmType = keyMember.TypeUsage.EdmType;
1100
1101             if (Helper.IsPrimitiveType(keyMemberEdmType))
1102             {
1103                 Type entitySetKeyType = ((PrimitiveType)keyMemberEdmType).ClrEquivalentType;
1104                 if (entitySetKeyType != keyValue.GetType())
1105                 {
1106                     if (isArgumentException)
1107                     {
1108                         throw EntityUtil.IncorrectValueType(argumentName, keyMember.Name, entitySetKeyType.FullName, keyValue.GetType().FullName);
1109                     }
1110                     else
1111                     {
1112                         throw EntityUtil.IncorrectValueTypeInvalidOperation(keyMember.Name, entitySetKeyType.FullName, keyValue.GetType().FullName);
1113                     }
1114                 }
1115             }
1116             else
1117             {
1118                 Debug.Assert(Helper.IsEnumType(keyMember.TypeUsage.EdmType), "Enum type expected");
1119
1120                 EnumType expectedEnumType;
1121                 if (workspace.TryGetObjectSpaceType((EnumType)keyMemberEdmType, out expectedEnumType))
1122                 {
1123                     var expectedClrEnumType = ((ClrEnumType)expectedEnumType).ClrType;
1124                     if (expectedClrEnumType != keyValue.GetType())
1125                     {
1126                         if (isArgumentException)
1127                         {
1128                             throw EntityUtil.IncorrectValueType(argumentName, keyMember.Name, expectedClrEnumType.FullName, keyValue.GetType().FullName);
1129                         }
1130                         else
1131                         {
1132                             throw EntityUtil.IncorrectValueTypeInvalidOperation(keyMember.Name, expectedClrEnumType.FullName, keyValue.GetType().FullName);
1133                         }
1134                     }
1135                 }
1136                 else
1137                 {
1138                     if (isArgumentException)
1139                     {
1140                         throw EntityUtil.NoCorrespondingOSpaceTypeForEnumKeyField(argumentName, keyMember.Name, keyMemberEdmType.FullName);
1141                     }
1142                     else
1143                     {
1144                         throw EntityUtil.NoCorrespondingOSpaceTypeForEnumKeyFieldInvalidOperation(keyMember.Name, keyMemberEdmType.FullName);
1145                     }
1146                 }
1147             }
1148         }
1149
1150         /// <summary>
1151         /// Asserts that the "state" of the EntityKey is correct, by validating assumptions
1152         /// based on whether the key is a singleton, composite, or temporary.
1153         /// </summary>
1154         /// <param name="isTemporary">whether we expect this EntityKey to be marked temporary</param>
1155         [Conditional("DEBUG")]
1156         private void AssertCorrectState(EntitySetBase entitySet, bool isTemporary)
1157         {
1158             if (_singletonKeyValue != null)
1159             {
1160                 Debug.Assert(!isTemporary, "Singleton keys should not be expected to be temporary.");
1161                 Debug.Assert(_compositeKeyValues == null, "The EntityKey is marked as both a singleton key and a composite key - this is illegal.");
1162                 if (entitySet != null)
1163                 {
1164                     Debug.Assert(entitySet.ElementType.KeyMembers.Count == 1, "For a singleton key, the number of key fields must be exactly 1.");
1165                 }
1166             }
1167             else if (_compositeKeyValues != null)
1168             {
1169                 Debug.Assert(!isTemporary, "Composite keys should not be expected to be temporary.");
1170                 if (entitySet != null)
1171                 {
1172                     Debug.Assert(entitySet.ElementType.KeyMembers.Count > 1, "For a composite key, the number of key fields should be greater than 1.");
1173                     Debug.Assert(entitySet.ElementType.KeyMembers.Count == _compositeKeyValues.Length, "Incorrect number of values specified to composite key.");
1174                 }
1175                 for (int i = 0; i < _compositeKeyValues.Length; ++i)
1176                 {
1177                     Debug.Assert(_compositeKeyValues[i] != null, "Values passed to a composite EntityKey cannot be null.");
1178                 }
1179             }
1180             else if (!IsTemporary)
1181             {
1182                 // one of our static keys
1183                 Debug.Assert(!isTemporary, "Static keys should not be expected to be temporary.");
1184                 Debug.Assert(this.EntityKeyValues == null, "The EntityKeyValues property for Static EntityKeys must return null.");
1185                 Debug.Assert(this.EntityContainerName == null, "The EntityContainerName property for Static EntityKeys must return null.");
1186                 Debug.Assert(this.EntitySetName != null, "The EntitySetName property for Static EntityKeys must not return null.");
1187             }
1188             else
1189             {
1190                 Debug.Assert(isTemporary, "The EntityKey is marked as neither a singleton or composite.  Therefore, it should be expected to be temporary.");
1191                 Debug.Assert(this.IsTemporary, "The EntityKey is marked as neither a singleton or composite.  Therefore it must be marked as temporary.");
1192                 Debug.Assert(this.EntityKeyValues == null, "The EntityKeyValues property for temporary EntityKeys must return null.");
1193             }
1194         }
1195
1196         #endregion
1197
1198         #region Serialization
1199
1200         /// <summary>
1201         /// 
1202         /// </summary>
1203         /// <param name="context"></param>
1204         [EditorBrowsable(EditorBrowsableState.Never)]
1205         [Browsable(false)]
1206         [OnDeserializing]
1207         public void OnDeserializing(StreamingContext context)
1208         {
1209             if (RequiresDeserialization)
1210             {
1211                 DeserializeMembers();
1212             }
1213         }
1214
1215         /// <summary>
1216         /// 
1217         /// </summary>
1218         /// <param name="context"></param>
1219         [OnDeserialized]
1220         [EditorBrowsable(EditorBrowsableState.Never)]
1221         [Browsable(false)]
1222         public void OnDeserialized(StreamingContext context)
1223         {
1224             lock (_nameLookup)
1225             {
1226                 _entitySetName = LookupSingletonName(_entitySetName);
1227                 _entityContainerName = LookupSingletonName(_entityContainerName);
1228                 if (_keyNames != null)
1229                 {
1230                     for (int i = 0; i < _keyNames.Length; i++)
1231                     {
1232                         _keyNames[i] = LookupSingletonName(_keyNames[i]);
1233                     }
1234                 }
1235             }
1236         }
1237
1238         /// <summary>
1239         /// Dev Note: this must be called from within a _lock block on _nameLookup
1240         /// </summary>
1241         /// <param name="name"></param>
1242         /// <returns></returns>
1243         private static string LookupSingletonName(string name)
1244         {
1245             if (String.IsNullOrEmpty(name))
1246             {
1247                 return null;
1248             }
1249             if (_nameLookup.ContainsKey(name))
1250             {
1251                 return _nameLookup[name];
1252             }
1253             _nameLookup.Add(name, name);
1254             return name;
1255         }
1256
1257         private void ValidateWritable(object instance)
1258         {
1259             if (_isLocked || instance != null)
1260             {
1261                 throw EntityUtil.CannotChangeEntityKey();
1262             }
1263         }
1264
1265         private bool RequiresDeserialization
1266         {
1267             get { return _deserializedMembers != null; }
1268         }
1269
1270         private void DeserializeMembers()
1271         {
1272             if (CheckKeyValues(new KeyValueReader(_deserializedMembers), true, true, out _keyNames, out _singletonKeyValue, out _compositeKeyValues))
1273             {
1274                 // If we received values from the _deserializedMembers, then we do not need to track these any more
1275                 _deserializedMembers = null;
1276             }
1277         }
1278
1279         #endregion
1280
1281         private class KeyValueReader : IEnumerable<KeyValuePair<string, object>>
1282         {
1283             IEnumerable<EntityKeyMember> _enumerator;
1284
1285             public KeyValueReader(IEnumerable<EntityKeyMember> enumerator)
1286             {
1287                 _enumerator = enumerator;
1288             }
1289
1290             #region IEnumerable<KeyValuePair<string,object>> Members
1291
1292             public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
1293             {
1294                 foreach (EntityKeyMember pair in _enumerator)
1295                 {
1296                     if (pair != null)
1297                     {
1298                         yield return new KeyValuePair<string, object>(pair.Key, pair.Value);
1299                     }
1300                 }
1301             }
1302
1303             #endregion
1304
1305             #region IEnumerable Members
1306
1307             IEnumerator IEnumerable.GetEnumerator()
1308             {
1309                 return this.GetEnumerator();
1310             }
1311
1312             #endregion
1313         }
1314     }
1315
1316     /// <summary>
1317     /// Information about a key that is part of an EntityKey.
1318     /// A key member contains the key name and value.
1319     /// </summary>
1320     [DataContract]
1321     [Serializable]
1322     public class EntityKeyMember
1323     {
1324         private string _keyName;
1325         private object _keyValue;
1326
1327         /// <summary>
1328         /// Creates an empty EntityKeyMember. This constructor is used by serialization.
1329         /// </summary>
1330         public EntityKeyMember()
1331         {
1332         }
1333
1334         /// <summary>
1335         /// Creates a new EntityKeyMember with the specified key name and value.
1336         /// </summary>
1337         /// <param name="keyName">The key name</param>
1338         /// <param name="keyValue">The key value</param>
1339         public EntityKeyMember(string keyName, object keyValue)
1340         {
1341             EntityUtil.CheckArgumentNull(keyName, "keyName");
1342             EntityUtil.CheckArgumentNull(keyValue, "keyValue");
1343             _keyName = keyName;
1344             _keyValue = keyValue;
1345         }
1346
1347         /// <summary>
1348         /// The key name
1349         /// </summary>
1350         [DataMember]
1351         public string Key
1352         {
1353             get
1354             {
1355                 return _keyName;
1356             }
1357             set
1358             {
1359                 ValidateWritable(_keyName);
1360                 EntityUtil.CheckArgumentNull(value, "value");
1361                 _keyName = value;
1362             }
1363         }
1364
1365         /// <summary>
1366         /// The key value
1367         /// </summary>
1368         [DataMember]
1369         public object Value
1370         {
1371             get
1372             {
1373                 return _keyValue;
1374             }
1375             set
1376             {
1377                 ValidateWritable(_keyValue);
1378                 EntityUtil.CheckArgumentNull(value, "value");
1379                 _keyValue = value;
1380             }
1381         }
1382
1383         /// <summary>
1384         /// Returns a string representation of the EntityKeyMember
1385         /// </summary>
1386         /// <returns>A string representation of the EntityKeyMember</returns>
1387         public override string ToString()
1388         {
1389             return String.Format(System.Globalization.CultureInfo.CurrentCulture, "[{0}, {1}]", _keyName, _keyValue);
1390         }
1391
1392         /// <summary>
1393         /// Ensures that the instance can be written to (value must be null)
1394         /// </summary>
1395         private void ValidateWritable(object instance)
1396         {
1397             if (instance != null)
1398             {
1399                 throw EntityUtil.CannotChangeEntityKey();
1400             }
1401         }
1402     }
1403 }
1404