1 //---------------------------------------------------------------------
2 // <copyright file="EntityKey.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
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
;
25 /// An identifier for an entity.
27 [DebuggerDisplay("{ConcatKeyValue()}")]
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.
59 private bool _containsByteArray
;
62 private EntityKeyMember
[] _deserializedMembers
;
64 // The hash code is not serialized since it can be computed differently on the deserialized system.
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";
74 /// A singleton EntityKey by which a read-only entity is identified.
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
);
80 /// A singleton EntityKey identifying an entity resulted from a failed TREAT.
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
);
86 /// A dictionary of names so that singleton instances of names can be used
88 private static Dictionary
<string, string> _nameLookup
= new Dictionary
<string, string>();
90 #region Public Constructors
93 /// Constructs an empty EntityKey. For use during XmlSerialization.
101 /// Constructs an EntityKey with the given key values.
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);
114 /// Constructs an EntityKey with the given key values.
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);
128 /// Constructs an EntityKey with the given single key name and value.
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);
150 #region Internal Constructors
153 /// Constructs an EntityKey from an IExtendedDataRecord representing the entity.
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);
174 /// Constructs an EntityKey from an IExtendedDataRecord representing the entity.
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
);
185 /// Constructs a temporary EntityKey with the given EntitySet.
186 /// Temporary keys do not store key field names
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);
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.
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);
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.
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);
244 /// Gets the EntitySet name identifying the entity set that contains the entity.
247 public string EntitySetName
251 return _entitySetName
;
255 ValidateWritable(_entitySetName
);
258 _entitySetName
= EntityKey
.LookupSingletonName(value);
264 /// Gets the EntityContainer name identifying the entity container that contains the entity.
267 public string EntityContainerName
271 return _entityContainerName
;
275 ValidateWritable(_entityContainerName
);
278 _entityContainerName
= EntityKey
.LookupSingletonName(value);
285 /// Gets the key values that identify the entity.
288 [System
.Diagnostics
.CodeAnalysis
.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification
= "Required for this feature")]
289 public EntityKeyMember
[] EntityKeyValues
295 EntityKeyMember
[] keyValues
;
296 if (_singletonKeyValue
!= null)
298 keyValues
= new EntityKeyMember
[] {
299 new EntityKeyMember(_keyNames
[0], _singletonKeyValue
) };
303 keyValues
= new EntityKeyMember
[_compositeKeyValues
.Length
];
304 for (int i
= 0; i
< _compositeKeyValues
.Length
; ++i
)
306 keyValues
[i
] = new EntityKeyMember(_keyNames
[i
], _compositeKeyValues
[i
]);
318 ValidateWritable(_keyNames
);
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;
332 /// Gets a value indicating whether this key is a temporary key.
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
;
367 /// Gets the entity set for this entity key from the given metadata workspace, by
368 /// entity container name and entity set name.
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
395 /// Compares this instance to a given key by their values.
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);
405 /// Compares this instance to a given key by their values.
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);
415 /// Returns a value-based hash code, to allow EntityKey to be used in hash tables.
417 /// <returns>the hash value of this EntityKey</returns>
418 public override int GetHashCode()
420 int hashCode
= _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
]);
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
;
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;
479 return hashCode ^ keyValue
.GetHashCode();
484 /// Compares two keys by their values.
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
)
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()");
500 return InternalEquals(key1
, key2
, compareEntitySets
: true);
504 /// Compares two keys by their values.
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
)
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()");
519 return !InternalEquals(key1
, key2
, compareEntitySets
: true);
523 /// Internal function to compare two keys by their values.
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
))
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))
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
)
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
)
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
))
575 if (!key1
._singletonKeyValue
.Equals(key2
._singletonKeyValue
))
582 if (!String
.Equals(key1
._keyNames
[0], key2
._keyNames
[0]))
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
))
604 if (!CompositeValuesEqual(key1
, key2
))
616 if (compareEntitySets
)
619 if (!String
.Equals(key1
._entitySetName
, key2
._entitySetName
) ||
620 !String
.Equals(key1
._entityContainerName
, key2
._entityContainerName
))
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
]))
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
))
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
]);
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
]))
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
))
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
]);
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.
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;
714 if (_singletonKeyValue
!= null)
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.");
746 new[] { DbExpressionBuilder
.Constant(Helper
.GetModelTypeUsage(singletonKeyMember
), _singletonKeyValue
)
747 .As(singletonKeyMember
.Name
) };
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
);
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.
771 internal string ConcatKeyValue()
773 System
.Text
.StringBuilder builder
= new System
.Text
.StringBuilder();
774 builder
.Append("EntitySet=").Append(_entitySetName
);
777 foreach (EntityKeyMember pair
in EntityKeyValues
)
780 builder
.Append(pair
.Key
).Append("=").Append(pair
.Value
);
784 return builder
.ToString();
788 /// Returns the appropriate value for the given key name.
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
;
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
)
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;
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)
877 throw EntityUtil
.EntityKeyMustHaveValues("entityKeyValues");
882 keyNames
= new string[numExpectedKeyValues
];
884 if (numExpectedKeyValues
== 1)
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
;
902 compositeKeyValues
= new object[numExpectedKeyValues
];
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
;
922 return numExpectedKeyValues
> 0;
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.
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");
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");
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.
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);
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.
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);
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
);
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
);
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
);
1067 // 3. Validate Key Name (if we found it or not)
1070 if (isArgumentException
)
1072 throw EntityUtil
.MissingKeyValue(argumentName
, keyField
.Name
, entitySet
.ElementType
.FullName
);
1076 throw EntityUtil
.MissingKeyValueInvalidOperation(keyField
.Name
, entitySet
.ElementType
.FullName
);
1085 /// Validates whether type of the key matches the type of the key value.
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
);
1112 throw EntityUtil
.IncorrectValueTypeInvalidOperation(keyMember
.Name
, entitySetKeyType
.FullName
, keyValue
.GetType().FullName
);
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
);
1132 throw EntityUtil
.IncorrectValueTypeInvalidOperation(keyMember
.Name
, expectedClrEnumType
.FullName
, keyValue
.GetType().FullName
);
1138 if (isArgumentException
)
1140 throw EntityUtil
.NoCorrespondingOSpaceTypeForEnumKeyField(argumentName
, keyMember
.Name
, keyMemberEdmType
.FullName
);
1144 throw EntityUtil
.NoCorrespondingOSpaceTypeForEnumKeyFieldInvalidOperation(keyMember
.Name
, keyMemberEdmType
.FullName
);
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.
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.");
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.");
1198 #region Serialization
1203 /// <param name="context"></param>
1204 [EditorBrowsable(EditorBrowsableState
.Never
)]
1207 public void OnDeserializing(StreamingContext context
)
1209 if (RequiresDeserialization
)
1211 DeserializeMembers();
1218 /// <param name="context"></param>
1220 [EditorBrowsable(EditorBrowsableState
.Never
)]
1222 public void OnDeserialized(StreamingContext context
)
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
]);
1239 /// Dev Note: this must be called from within a _lock block on _nameLookup
1241 /// <param name="name"></param>
1242 /// <returns></returns>
1243 private static string LookupSingletonName(string name
)
1245 if (String
.IsNullOrEmpty(name
))
1249 if (_nameLookup
.ContainsKey(name
))
1251 return _nameLookup
[name
];
1253 _nameLookup
.Add(name
, 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;
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
)
1298 yield return new KeyValuePair
<string, object>(pair
.Key
, pair
.Value
);
1305 #region IEnumerable Members
1307 IEnumerator IEnumerable
.GetEnumerator()
1309 return this.GetEnumerator();
1317 /// Information about a key that is part of an EntityKey.
1318 /// A key member contains the key name and value.
1322 public class EntityKeyMember
1324 private string _keyName
;
1325 private object _keyValue
;
1328 /// Creates an empty EntityKeyMember. This constructor is used by serialization.
1330 public EntityKeyMember()
1335 /// Creates a new EntityKeyMember with the specified key name and value.
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");
1344 _keyValue
= keyValue
;
1359 ValidateWritable(_keyName
);
1360 EntityUtil
.CheckArgumentNull(value, "value");
1377 ValidateWritable(_keyValue
);
1378 EntityUtil
.CheckArgumentNull(value, "value");
1384 /// Returns a string representation of the EntityKeyMember
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
);
1393 /// Ensures that the instance can be written to (value must be null)
1395 private void ValidateWritable(object instance
)
1397 if (instance
!= null)
1399 throw EntityUtil
.CannotChangeEntityKey();