Updates referencesource to .NET 4.7
[mono-project.git] / mcs / class / referencesource / System.Data.Entity / System / Data / EntityKey.cs
blob84c60eb7e870df2a79fb61016896a9fe5a48d46a
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 //---------------------------------------------------------------------
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;
22 namespace System.Data
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>
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.
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
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;
61 [NonSerialized]
62 private EntityKeyMember[] _deserializedMembers;
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
69 // Names for constant EntityKeys
70 private const string s_NoEntitySetKey = "NoEntitySetKey.NoEntitySetKey";
71 private const string s_EntityNotValidKey = "EntityNotValidKey.EntityNotValidKey";
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);
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);
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>();
90 #region Public Constructors
92 /// <summary>
93 /// Constructs an empty EntityKey. For use during XmlSerialization.
94 /// </summary>
95 public EntityKey()
97 _isLocked = false;
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)
107 GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
108 CheckKeyValues(entityKeyValues, out _keyNames, out _singletonKeyValue, out _compositeKeyValues);
109 AssertCorrectState(null, false);
110 _isLocked = true;
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)
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;
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)
135 GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
136 EntityUtil.CheckStringArgument(keyName, "keyName");
137 EntityUtil.CheckArgumentNull(keyValue, "keyValue");
139 _keyNames = new string[1];
140 ValidateName(keyName);
141 _keyNames[0] = keyName;
142 _singletonKeyValue = keyValue;
144 AssertCorrectState(null, false);
145 _isLocked = true;
148 #endregion
150 #region Internal Constructors
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)
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");
165 _entitySetName = entitySet.Name;
166 _entityContainerName = entitySet.EntityContainer.Name;
168 GetKeyValues(entitySet, record, out _keyNames, out _singletonKeyValue, out _compositeKeyValues);
169 AssertCorrectState(entitySet, false);
170 _isLocked = true;
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)
180 GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
181 _isLocked = true;
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)
191 EntityUtil.CheckArgumentNull(entitySet, "entitySet");
192 Debug.Assert(entitySet.EntityContainer != null, "EntitySet.EntityContainer cannot be null.");
194 _entitySetName = entitySet.Name;
195 _entityContainerName = entitySet.EntityContainer.Name;
197 AssertCorrectState(entitySet, true);
198 _isLocked = true;
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)
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;
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)
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;
241 #endregion
243 /// <summary>
244 /// Gets the EntitySet name identifying the entity set that contains the entity.
245 /// </summary>
246 [DataMember]
247 public string EntitySetName
251 return _entitySetName;
255 ValidateWritable(_entitySetName);
256 lock (_nameLookup)
258 _entitySetName = EntityKey.LookupSingletonName(value);
263 /// <summary>
264 /// Gets the EntityContainer name identifying the entity container that contains the entity.
265 /// </summary>
266 [DataMember]
267 public string EntityContainerName
271 return _entityContainerName;
275 ValidateWritable(_entityContainerName);
276 lock (_nameLookup)
278 _entityContainerName = EntityKey.LookupSingletonName(value);
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
293 if (!IsTemporary)
295 EntityKeyMember[] keyValues;
296 if (_singletonKeyValue != null)
298 keyValues = new EntityKeyMember[] {
299 new EntityKeyMember(_keyNames[0], _singletonKeyValue) };
301 else
303 keyValues = new EntityKeyMember[_compositeKeyValues.Length];
304 for (int i = 0; i < _compositeKeyValues.Length; ++i)
306 keyValues[i] = new EntityKeyMember(_keyNames[i], _compositeKeyValues[i]);
309 return keyValues;
311 else
313 return null;
318 ValidateWritable(_keyNames);
319 if (value != null)
321 if (!CheckKeyValues(new KeyValueReader(value), true, true, out _keyNames, out _singletonKeyValue, out _compositeKeyValues))
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;
331 /// <summary>
332 /// Gets a value indicating whether this key is a temporary key.
333 /// </summary>
334 public bool IsTemporary
338 return (SingletonKeyValue == null) && (CompositeKeyValues == null);
342 private object SingletonKeyValue
346 if (RequiresDeserialization)
348 DeserializeMembers();
350 return _singletonKeyValue;
354 private object[] CompositeKeyValues
358 if (RequiresDeserialization)
360 DeserializeMembers();
362 return _compositeKeyValues;
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)
375 EntityUtil.CheckArgumentNull(metadataWorkspace, "metadataWorkspace");
376 if (String.IsNullOrEmpty(_entityContainerName) || String.IsNullOrEmpty(_entitySetName))
378 throw EntityUtil.MissingQualifiedEntitySetName();
381 // GetEntityContainer will throw if it cannot find the container
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.
387 return metadataWorkspace
388 .GetEntityContainer(_entityContainerName, DataSpace.CSpace)
389 .GetEntitySetByName(_entitySetName, false);
392 #region Equality/Hashing
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)
401 return InternalEquals(this, obj as EntityKey, compareEntitySets: true);
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)
411 return InternalEquals(this, other, compareEntitySets: true);
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()
420 int hashCode = _hashCode;
421 if (0 == hashCode)
423 _containsByteArray = false;
425 if (RequiresDeserialization)
427 DeserializeMembers();
430 if (_entitySetName != null)
432 hashCode = _entitySetName.GetHashCode();
434 if (_entityContainerName != null)
436 hashCode ^= _entityContainerName.GetHashCode();
439 // If the key is not temporary, determine a hash code based on the value(s) within the key.
440 if (null != _singletonKeyValue)
442 hashCode = AddHashValue(hashCode, _singletonKeyValue);
444 else if (null != _compositeKeyValues)
446 for (int i = 0, n = _compositeKeyValues.Length; i < n; i++)
448 hashCode = AddHashValue(hashCode, _compositeKeyValues[i]);
451 else
453 // If the key is temporary, use default hash code
454 hashCode = base.GetHashCode();
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)))
462 _hashCode = hashCode;
465 return hashCode;
468 private int AddHashValue(int hashCode, object keyValue)
470 byte[] byteArrayValue = keyValue as byte[];
471 if (null != byteArrayValue)
473 hashCode ^= ByValueEqualityComparer.ComputeBinaryHashCode(byteArrayValue);
474 _containsByteArray = true;
475 return hashCode;
477 else
479 return hashCode ^ keyValue.GetHashCode();
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)
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
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()");
499 #endif
500 return InternalEquals(key1, key2, compareEntitySets: true);
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)
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
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()");
518 #endif
519 return !InternalEquals(key1, key2, compareEntitySets: true);
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)
531 // If both are null or refer to the same object, they're equal.
532 if (object.ReferenceEquals(key1, key2))
534 return true;
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))
540 return false;
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.
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)
553 return false;
556 if (null != key1._singletonKeyValue)
558 if (key1._containsByteArray)
560 // Compare the single value (if the second is null, false should be returned)
561 if (null == key2._singletonKeyValue)
563 return false;
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))
569 return false;
572 else
574 // not a byte array
575 if (!key1._singletonKeyValue.Equals(key2._singletonKeyValue))
577 return false;
581 // Check key names
582 if (!String.Equals(key1._keyNames[0], key2._keyNames[0]))
584 return false;
587 else
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)
595 if (key1._containsByteArray)
597 if (!CompositeValuesWithBinaryEqual(key1, key2))
599 return false;
602 else
604 if (!CompositeValuesEqual(key1, key2))
606 return false;
610 else
612 return false;
616 if (compareEntitySets)
618 // Check metadata.
619 if (!String.Equals(key1._entitySetName, key2._entitySetName) ||
620 !String.Equals(key1._entityContainerName, key2._entityContainerName))
622 return false;
626 return true;
629 internal static bool CompositeValuesWithBinaryEqual(EntityKey key1, EntityKey key2)
631 for (int i = 0; i < key1._compositeKeyValues.Length; ++i)
633 if (key1._keyNames[i].Equals(key2._keyNames[i]))
635 if (!ByValueEqualityComparer.Default.Equals(key1._compositeKeyValues[i], key2._compositeKeyValues[i]))
637 return false;
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))
644 return false;
647 return true;
650 private static bool ValuesWithBinaryEqual(string keyName, object keyValue, EntityKey key2)
652 for (int i = 0; i < key2._keyNames.Length; i++)
654 if (String.Equals(keyName, key2._keyNames[i]))
656 return ByValueEqualityComparer.Default.Equals(keyValue, key2._compositeKeyValues[i]);
659 return false;
662 private static bool CompositeValuesEqual(EntityKey key1, EntityKey key2)
664 for (int i = 0; i < key1._compositeKeyValues.Length; ++i)
666 if (key1._keyNames[i].Equals(key2._keyNames[i]))
668 if (!Object.Equals(key1._compositeKeyValues[i], key2._compositeKeyValues[i]))
670 return false;
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))
677 return false;
680 return true;
683 private static bool ValuesEqual(string keyName, object keyValue, EntityKey key2)
685 for (int i = 0; i < key2._keyNames.Length; i++)
687 if (String.Equals(keyName, key2._keyNames[i]))
689 return Object.Equals(keyValue, key2._compositeKeyValues[i]);
692 return false;
695 #endregion
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)
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)
714 if (_singletonKeyValue != null)
716 numKeyMembers = 1;
718 else
720 numKeyMembers = _compositeKeyValues.Length;
723 if (((EntitySetBase)entitySet).ElementType.KeyMembers.Count != numKeyMembers)
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.
734 throw EntityUtil.EntitySetDoesNotMatch("metadataWorkspace", TypeHelpers.GetFullName(entitySet));
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)
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) };
750 else
752 keyColumns = new KeyValuePair<string, DbExpression>[_compositeKeyValues.Length];
753 for (int i = 0; i < _compositeKeyValues.Length; ++i)
755 Debug.Assert(_compositeKeyValues[i] != null, "Values within key-value pairs cannot be null.");
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);
763 return keyColumns;
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()
773 System.Text.StringBuilder builder = new System.Text.StringBuilder();
774 builder.Append("EntitySet=").Append(_entitySetName);
775 if (!IsTemporary)
777 foreach (EntityKeyMember pair in EntityKeyValues)
779 builder.Append(';');
780 builder.Append(pair.Key).Append("=").Append(pair.Value);
784 return builder.ToString();
787 /// <summary>
788 /// Returns the appropriate value for the given key name.
789 /// </summary>
790 internal object FindValueByName(string keyName)
792 Debug.Assert(!IsTemporary, "FindValueByName should not be called for temporary keys.");
793 if (SingletonKeyValue != null)
795 Debug.Assert(_keyNames[0] == keyName, "For a singleton key, the given keyName must match.");
796 return _singletonKeyValue;
798 else
800 object[] compositeKeyValues = CompositeKeyValues;
801 for (int i = 0; i < compositeKeyValues.Length; i++)
803 if (keyName == _keyNames[i])
805 return compositeKeyValues[i];
808 throw EntityUtil.ArgumentOutOfRange("keyName");
812 internal static void GetEntitySetName(string qualifiedEntitySetName, out string entitySet, out string container)
814 entitySet = null;
815 container = null;
816 EntityUtil.CheckStringArgument(qualifiedEntitySetName, "qualifiedEntitySetName");
818 string[] result = qualifiedEntitySetName.Split('.');
819 if (result.Length != 2)
821 throw EntityUtil.InvalidQualifiedEntitySetName();
824 container = result[0];
825 entitySet = result[1];
827 // both parts must be non-empty
828 if (container == null || container.Length == 0 ||
829 entitySet == null || entitySet.Length == 0)
831 throw EntityUtil.InvalidQualifiedEntitySetName();
834 ValidateName(container);
835 ValidateName(entitySet);
838 internal static void ValidateName(string name)
840 if (!System.Data.EntityModel.SchemaObjectModel.Utils.ValidUndottedName(name))
842 throw EntityUtil.EntityKeyInvalidName(name);
846 #region Key Value Assignment and Validation
848 private static bool CheckKeyValues(IEnumerable<KeyValuePair<string, object>> entityKeyValues,
849 out string[] keyNames, out object singletonKeyValue, out object[] compositeKeyValues)
851 return CheckKeyValues(entityKeyValues, false, false, out keyNames, out singletonKeyValue, out compositeKeyValues);
854 private static bool CheckKeyValues(IEnumerable<KeyValuePair<string, object>> entityKeyValues, bool allowNullKeys, bool tokenizeStrings,
855 out string[] keyNames, out object singletonKeyValue, out object[] compositeKeyValues)
857 EntityUtil.CheckArgumentNull(entityKeyValues, "entityKeyValues");
859 int numExpectedKeyValues;
860 int numActualKeyValues = 0;
862 keyNames = null;
863 singletonKeyValue = null;
864 compositeKeyValues = null;
866 // Determine if we're a single or composite key.
867 foreach (KeyValuePair<string, object> value in entityKeyValues)
869 numActualKeyValues++;
872 numExpectedKeyValues = numActualKeyValues;
873 if (numExpectedKeyValues == 0)
875 if (!allowNullKeys)
877 throw EntityUtil.EntityKeyMustHaveValues("entityKeyValues");
880 else
882 keyNames = new string[numExpectedKeyValues];
884 if (numExpectedKeyValues == 1)
886 lock (_nameLookup)
888 foreach (KeyValuePair<string, object> keyValuePair in entityKeyValues)
890 if (EntityUtil.IsNull(keyValuePair.Value) || String.IsNullOrEmpty(keyValuePair.Key))
892 throw EntityUtil.NoNullsAllowedInKeyValuePairs("entityKeyValues");
894 ValidateName(keyValuePair.Key);
895 keyNames[0] = tokenizeStrings ? EntityKey.LookupSingletonName(keyValuePair.Key) : keyValuePair.Key;
896 singletonKeyValue = keyValuePair.Value;
900 else
902 compositeKeyValues = new object[numExpectedKeyValues];
904 int i = 0;
905 lock (_nameLookup)
907 foreach (KeyValuePair<string, object> keyValuePair in entityKeyValues)
909 if (EntityUtil.IsNull(keyValuePair.Value) || String.IsNullOrEmpty(keyValuePair.Key))
911 throw EntityUtil.NoNullsAllowedInKeyValuePairs("entityKeyValues");
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++;
922 return numExpectedKeyValues > 0;
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)
939 singletonKeyValue = null;
940 compositeKeyValues = null;
942 int numExpectedKeyValues = ((EntitySetBase)entitySet).ElementType.KeyMembers.Count;
943 keyNames = ((EntitySetBase)entitySet).ElementType.KeyMemberNames;
945 EntityType entityType = record.DataRecordInfo.RecordType.EdmType as EntityType;
946 Debug.Assert(entityType != null, "Data record must be an entity.");
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.");
952 if (numExpectedKeyValues == 1)
954 // Optimize for a singleton key.
956 EdmMember member = entityType.KeyMembers[0];
957 singletonKeyValue = record[member.Name];
958 if (EntityUtil.IsNull(singletonKeyValue))
960 throw EntityUtil.NoNullsAllowedInKeyValuePairs("record");
963 else
965 compositeKeyValues = new object[numExpectedKeyValues];
966 // grab each key-field from the data record
967 for (int i = 0; i < numExpectedKeyValues; ++i)
969 EdmMember member = entityType.KeyMembers[i];
970 compositeKeyValues[i] = record[member.Name];
971 if (EntityUtil.IsNull(compositeKeyValues[i]))
973 throw EntityUtil.NoNullsAllowedInKeyValuePairs("record");
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)
989 ValidateEntityKey(workspace, entitySet, false, null);
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)
1003 if (entitySet != null)
1005 ReadOnlyMetadataCollection<EdmMember> keyMembers = ((EntitySetBase)entitySet).ElementType.KeyMembers;
1006 if (_singletonKeyValue != null)
1008 // 1. Validate number of keys
1009 if (keyMembers.Count != 1)
1011 if (isArgumentException)
1013 throw EntityUtil.IncorrectNumberOfKeyValuePairs(argumentName, entitySet.ElementType.FullName, keyMembers.Count, 1);
1015 else
1017 throw EntityUtil.IncorrectNumberOfKeyValuePairsInvalidOperation(entitySet.ElementType.FullName, keyMembers.Count, 1);
1021 // 2. Validate type of key values
1022 ValidateTypeOfKeyValue(workspace, keyMembers[0], _singletonKeyValue, isArgumentException, argumentName);
1024 // 3. Validate key names
1025 if (_keyNames[0] != keyMembers[0].Name)
1027 if (isArgumentException)
1029 throw EntityUtil.MissingKeyValue(argumentName, keyMembers[0].Name, entitySet.ElementType.FullName);
1031 else
1033 throw EntityUtil.MissingKeyValueInvalidOperation(keyMembers[0].Name, entitySet.ElementType.FullName);
1037 else if (null != _compositeKeyValues)
1039 // 1. Validate number of keys
1040 if (keyMembers.Count != _compositeKeyValues.Length)
1042 if (isArgumentException)
1044 throw EntityUtil.IncorrectNumberOfKeyValuePairs(argumentName, entitySet.ElementType.FullName, keyMembers.Count, _compositeKeyValues.Length);
1046 else
1048 throw EntityUtil.IncorrectNumberOfKeyValuePairsInvalidOperation(entitySet.ElementType.FullName, keyMembers.Count, _compositeKeyValues.Length);
1052 for (int i = 0; i < _compositeKeyValues.Length; ++i)
1054 EdmMember keyField = ((EntitySetBase)entitySet).ElementType.KeyMembers[i];
1055 bool foundMember = false;
1056 for (int j = 0; j < _compositeKeyValues.Length; ++j)
1058 if (keyField.Name == _keyNames[j])
1060 // 2. Validate type of key values
1061 ValidateTypeOfKeyValue(workspace, keyField, _compositeKeyValues[j], isArgumentException, argumentName);
1063 foundMember = true;
1064 break;
1067 // 3. Validate Key Name (if we found it or not)
1068 if (!foundMember)
1070 if (isArgumentException)
1072 throw EntityUtil.MissingKeyValue(argumentName, keyField.Name, entitySet.ElementType.FullName);
1074 else
1076 throw EntityUtil.MissingKeyValueInvalidOperation(keyField.Name, entitySet.ElementType.FullName);
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)
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");
1099 EdmType keyMemberEdmType = keyMember.TypeUsage.EdmType;
1101 if (Helper.IsPrimitiveType(keyMemberEdmType))
1103 Type entitySetKeyType = ((PrimitiveType)keyMemberEdmType).ClrEquivalentType;
1104 if (entitySetKeyType != keyValue.GetType())
1106 if (isArgumentException)
1108 throw EntityUtil.IncorrectValueType(argumentName, keyMember.Name, entitySetKeyType.FullName, keyValue.GetType().FullName);
1110 else
1112 throw EntityUtil.IncorrectValueTypeInvalidOperation(keyMember.Name, entitySetKeyType.FullName, keyValue.GetType().FullName);
1116 else
1118 Debug.Assert(Helper.IsEnumType(keyMember.TypeUsage.EdmType), "Enum type expected");
1120 EnumType expectedEnumType;
1121 if (workspace.TryGetObjectSpaceType((EnumType)keyMemberEdmType, out expectedEnumType))
1123 var expectedClrEnumType = ((ClrEnumType)expectedEnumType).ClrType;
1124 if (expectedClrEnumType != keyValue.GetType())
1126 if (isArgumentException)
1128 throw EntityUtil.IncorrectValueType(argumentName, keyMember.Name, expectedClrEnumType.FullName, keyValue.GetType().FullName);
1130 else
1132 throw EntityUtil.IncorrectValueTypeInvalidOperation(keyMember.Name, expectedClrEnumType.FullName, keyValue.GetType().FullName);
1136 else
1138 if (isArgumentException)
1140 throw EntityUtil.NoCorrespondingOSpaceTypeForEnumKeyField(argumentName, keyMember.Name, keyMemberEdmType.FullName);
1142 else
1144 throw EntityUtil.NoCorrespondingOSpaceTypeForEnumKeyFieldInvalidOperation(keyMember.Name, keyMemberEdmType.FullName);
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)
1158 if (_singletonKeyValue != null)
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)
1164 Debug.Assert(entitySet.ElementType.KeyMembers.Count == 1, "For a singleton key, the number of key fields must be exactly 1.");
1167 else if (_compositeKeyValues != null)
1169 Debug.Assert(!isTemporary, "Composite keys should not be expected to be temporary.");
1170 if (entitySet != null)
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.");
1175 for (int i = 0; i < _compositeKeyValues.Length; ++i)
1177 Debug.Assert(_compositeKeyValues[i] != null, "Values passed to a composite EntityKey cannot be null.");
1180 else if (!IsTemporary)
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.");
1188 else
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.");
1196 #endregion
1198 #region Serialization
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)
1209 if (RequiresDeserialization)
1211 DeserializeMembers();
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)
1224 lock (_nameLookup)
1226 _entitySetName = LookupSingletonName(_entitySetName);
1227 _entityContainerName = LookupSingletonName(_entityContainerName);
1228 if (_keyNames != null)
1230 for (int i = 0; i < _keyNames.Length; i++)
1232 _keyNames[i] = LookupSingletonName(_keyNames[i]);
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)
1245 if (String.IsNullOrEmpty(name))
1247 return null;
1249 if (_nameLookup.ContainsKey(name))
1251 return _nameLookup[name];
1253 _nameLookup.Add(name, name);
1254 return name;
1257 private void ValidateWritable(object instance)
1259 if (_isLocked || instance != null)
1261 throw EntityUtil.CannotChangeEntityKey();
1265 private bool RequiresDeserialization
1267 get { return _deserializedMembers != null; }
1270 private void DeserializeMembers()
1272 if (CheckKeyValues(new KeyValueReader(_deserializedMembers), true, true, out _keyNames, out _singletonKeyValue, out _compositeKeyValues))
1274 // If we received values from the _deserializedMembers, then we do not need to track these any more
1275 _deserializedMembers = null;
1279 #endregion
1281 private class KeyValueReader : IEnumerable<KeyValuePair<string, object>>
1283 IEnumerable<EntityKeyMember> _enumerator;
1285 public KeyValueReader(IEnumerable<EntityKeyMember> enumerator)
1287 _enumerator = enumerator;
1290 #region IEnumerable<KeyValuePair<string,object>> Members
1292 public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
1294 foreach (EntityKeyMember pair in _enumerator)
1296 if (pair != null)
1298 yield return new KeyValuePair<string, object>(pair.Key, pair.Value);
1303 #endregion
1305 #region IEnumerable Members
1307 IEnumerator IEnumerable.GetEnumerator()
1309 return this.GetEnumerator();
1312 #endregion
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
1324 private string _keyName;
1325 private object _keyValue;
1327 /// <summary>
1328 /// Creates an empty EntityKeyMember. This constructor is used by serialization.
1329 /// </summary>
1330 public EntityKeyMember()
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)
1341 EntityUtil.CheckArgumentNull(keyName, "keyName");
1342 EntityUtil.CheckArgumentNull(keyValue, "keyValue");
1343 _keyName = keyName;
1344 _keyValue = keyValue;
1347 /// <summary>
1348 /// The key name
1349 /// </summary>
1350 [DataMember]
1351 public string Key
1355 return _keyName;
1359 ValidateWritable(_keyName);
1360 EntityUtil.CheckArgumentNull(value, "value");
1361 _keyName = value;
1365 /// <summary>
1366 /// The key value
1367 /// </summary>
1368 [DataMember]
1369 public object Value
1373 return _keyValue;
1377 ValidateWritable(_keyValue);
1378 EntityUtil.CheckArgumentNull(value, "value");
1379 _keyValue = value;
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()
1389 return String.Format(System.Globalization.CultureInfo.CurrentCulture, "[{0}, {1}]", _keyName, _keyValue);
1392 /// <summary>
1393 /// Ensures that the instance can be written to (value must be null)
1394 /// </summary>
1395 private void ValidateWritable(object instance)
1397 if (instance != null)
1399 throw EntityUtil.CannotChangeEntityKey();