Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / ndp / fx / src / DataEntity / System / Data / EntityKey.cs / 4 / EntityKey.cs
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Diagnostics;
using System.Collections;
using System.Data.Common;
using System.Data.Common.Utils;
using System.Data.Metadata.Edm;
using System.Data.Mapping;
using System.Data.Objects;
using System.Data.Common.CommandTrees;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using System.Xml;
namespace System.Data
{
///
/// An identifier for an entity.
///
[DebuggerDisplay("{ConcatKeyValue()}")]
[Serializable]
[DataContract(IsReference=true)]
public sealed class EntityKey : IEquatable
{
// The implementation of EntityKey is optimized for the following common cases:
// 1) Keys constructed internally rather by the user - in particular, keys
// created by the bridge on the round-trip from query.
// 2) Single-valued (as opposed to composite) keys.
// We accomplish this by maintaining two variables, at most one of which is non-null.
// The first is of type object and in the case of a singleton key, is set to the
// single key value. The second is an object array and in the case of
// a composite key, is set to the list of key values. If both variables are null,
// the EntityKey is a temporary key. Note that the key field names
// are not stored - for composite keys, the values are stored in the order in which
// metadata reports the corresponding key members.
// The following 5 fields are serialized. Adding or removing a serialized field is considered
// a breaking change. This includes changing the field type or field name of existing
// serialized fields. If you need to make this kind of change, it may be possible, but it
// will require some custom serialization/deserialization code.
private string _entitySetName;
private string _entityContainerName;
private object _singletonKeyValue; // non-null for singleton keys
private object[] _compositeKeyValues; // non-null for composite keys
private string[] _keyNames; // key names that correspond to the key values
private bool _isLocked; // determines if this key is lock from writing
[NonSerialized]
private EntityKeyMember[] _deserializedMembers;
// The hash code is not serialized since it can be computed differently on the deserialized system.
[NonSerialized]
private int _hashCode; // computed as needed
// Names for constant EntityKeys
private const string s_NoEntitySetKey = "NoEntitySetKey.NoEntitySetKey";
private const string s_EntityNotValidKey = "EntityNotValidKey.EntityNotValidKey";
///
/// A singleton EntityKey by which a readonly entity is identified.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] // Justification: these are internal so they cannot be modified publically
public static readonly EntityKey NoEntitySetKey = new EntityKey(s_NoEntitySetKey);
///
/// A singleton EntityKey identifying an entity resulted from a failed TREAT.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] // Justification: these are internal so they cannot be modified publically
public static readonly EntityKey EntityNotValidKey = new EntityKey(s_EntityNotValidKey);
///
/// A dictionary of names so that singleton instances of names can be used
///
private static Dictionary _nameLookup = new Dictionary();
#region Public Constructors
///
/// Constructs an empty EntityKey. For use during XmlSerialization.
///
public EntityKey()
{
_isLocked = false;
}
///
/// Constructs an EntityKey with the given key values.
///
/// The EntitySet name, qualified by the EntityContainer name, of the entity
/// The key-value pairs that identify the entity
public EntityKey(string qualifiedEntitySetName, IEnumerable> entityKeyValues)
{
GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
CheckKeyValues(entityKeyValues, out _keyNames, out _singletonKeyValue, out _compositeKeyValues);
AssertCorrectState(null, false);
_isLocked = true;
}
///
/// Constructs an EntityKey with the given key values.
///
/// The EntitySet name, qualified by the EntityContainer name, of the entity
/// The key-value pairs that identify the entity
public EntityKey(string qualifiedEntitySetName, IEnumerable entityKeyValues)
{
GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
CheckKeyValues(new KeyValueReader(entityKeyValues), out _keyNames, out _singletonKeyValue, out _compositeKeyValues);
AssertCorrectState(null, false);
_isLocked = true;
}
///
/// Constructs an EntityKey with the given single key name and value.
///
/// The EntitySet name, qualified by the EntityContainer name, of the entity
/// The key name that identifies the entity
/// The key value that identifies the entity
public EntityKey(string qualifiedEntitySetName, string keyName, object keyValue)
{
GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
EntityUtil.CheckStringArgument(keyName, "keyName");
EntityUtil.CheckArgumentNull(keyValue, "keyValue");
_keyNames = new string[1];
ValidateName(keyName);
_keyNames[0] = keyName;
_singletonKeyValue = keyValue;
AssertCorrectState(null, false);
_isLocked = true;
}
#endregion
#region Internal Constructors
///
/// Constructs an EntityKey from an IExtendedDataRecord representing the entity.
///
/// EntitySet of the entity
/// an IExtendedDataRecord that represents the entity
internal EntityKey(EntitySet entitySet, IExtendedDataRecord record)
{
EntityUtil.CheckArgumentNull(entitySet, "entitySet");
EntityUtil.CheckArgumentNull(entitySet.Name, "entitySet.Name");
EntityUtil.CheckArgumentNull(entitySet.EntityContainer, "entitySet.Container");
EntityUtil.CheckArgumentNull(entitySet.EntityContainer.Name, "entitySet.Container.Name");
EntityUtil.CheckArgumentNull(record, "record");
_entitySetName = entitySet.Name;
_entityContainerName = entitySet.EntityContainer.Name;
CheckKeyValues(entitySet, record, out _keyNames, out _singletonKeyValue, out _compositeKeyValues);
AssertCorrectState(entitySet, false);
_isLocked = true;
}
///
/// Constructs an EntityKey from an IExtendedDataRecord representing the entity.
///
/// EntitySet of the entity
/// an IExtendedDataRecord that represents the entity
internal EntityKey(string qualifiedEntitySetName)
{
GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
_isLocked = true;
}
///
/// Constructs a temporary EntityKey with the given EntitySet.
/// Temporary keys do not store key field names
///
/// EntitySet of the entity
internal EntityKey(EntitySetBase entitySet)
{
EntityUtil.CheckArgumentNull(entitySet, "entitySet");
Debug.Assert(entitySet.EntityContainer != null, "EntitySet.EntityContainer cannot be null.");
_entitySetName = entitySet.Name;
_entityContainerName = entitySet.EntityContainer.Name;
AssertCorrectState(entitySet, true);
_isLocked = true;
}
///
/// Constructor optimized for a singleton key.
/// SQLBUDT 478655: Performance optimization: Does no integrity checking on the key value.
/// SQLBUDT 523554: Performance optimization: Does no validate type of key members.
///
/// EntitySet of the entity
/// The single value that composes the entity's key, assumed to contain the correct type.
internal EntityKey(EntitySetBase entitySet, object singletonKeyValue)
{
Debug.Assert(entitySet != null, "EntitySet cannot be null.");
Debug.Assert(entitySet.EntityContainer != null, "EntitySet.EntityContainer cannot be null.");
Debug.Assert(singletonKeyValue != null, "Singleton key value cannot be null.");
_singletonKeyValue = singletonKeyValue;
_entitySetName = entitySet.Name;
_entityContainerName = entitySet.EntityContainer.Name;
_keyNames = entitySet.ElementType.KeyMemberNames; // using EntitySetBase avoids an (EntityType) cast that EntitySet encoure
AssertCorrectState(entitySet, false);
_isLocked = true;
}
///
/// Constructor optimized for a composite key.
/// SQLBUDT 478655: Performance optimization: Does no integrity checking on the key values.
/// SQLBUDT 523554: Performance optimization: Does no validate type of key members.
///
/// EntitySet of the entity
/// A list of the values (at least 2) that compose the entity's key, assumed to contain correct types.
internal EntityKey(EntitySetBase entitySet, object[] compositeKeyValues)
{
Debug.Assert(entitySet != null, "EntitySet cannot be null.");
Debug.Assert(entitySet.EntityContainer != null, "EntitySet.EntityContainer cannot be null.");
Debug.Assert(compositeKeyValues != null, "Composite key values cannot be null.");
_compositeKeyValues = compositeKeyValues;
_entitySetName = entitySet.Name;
_entityContainerName = entitySet.EntityContainer.Name;
_keyNames = entitySet.ElementType.KeyMemberNames; // using EntitySetBase avoids an (EntityType) cast that EntitySet encoure
AssertCorrectState(entitySet, false);
_isLocked = true;
}
#endregion
///
/// Gets the EntitySet name identifying the entity set that contains the entity.
///
[DataMember]
public string EntitySetName
{
get
{
return _entitySetName;
}
set
{
ValidateWritable(_entitySetName);
lock(_nameLookup)
{
_entitySetName = EntityKey.LookupSingletonName(value);
}
}
}
///
/// Gets the EntityContainer name identifying the entity container that contains the entity.
///
[DataMember]
public string EntityContainerName
{
get
{
return _entityContainerName;
}
set
{
ValidateWritable(_entityContainerName);
lock (_nameLookup)
{
_entityContainerName = EntityKey.LookupSingletonName(value);
}
}
}
///
/// Gets the key values that identify the entity.
///
[DataMember]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification="Required for this feature")]
public EntityKeyMember[] EntityKeyValues
{
get
{
if(!IsTemporary)
{
EntityKeyMember[] keyValues;
if (_singletonKeyValue != null)
{
keyValues = new EntityKeyMember[] {
new EntityKeyMember(_keyNames[0], _singletonKeyValue) };
}
else
{
keyValues = new EntityKeyMember[_compositeKeyValues.Length];
for (int i = 0; i < _compositeKeyValues.Length; ++i)
{
keyValues[i] = new EntityKeyMember(_keyNames[i], _compositeKeyValues[i]);
}
}
return keyValues;
}
else
{
return null;
}
}
set
{
ValidateWritable(_keyNames);
if (value != null)
{
if (!CheckKeyValues(new KeyValueReader(value), true, true, out _keyNames, out _singletonKeyValue, out _compositeKeyValues))
{
// If we did not retrieve values from the setter (i.e. encoded settings), we need to keep track of the
// array instance because the array members will be set next.
_deserializedMembers = value;
}
}
}
}
///
/// Gets a value indicating whether this key is a temporary key.
///
public bool IsTemporary
{
get
{
return (SingletonKeyValue == null) && (CompositeKeyValues == null);
}
}
private object SingletonKeyValue
{
get
{
if (RequiresDeserialization)
{
DeserializeMembers();
}
return _singletonKeyValue;
}
}
private object[] CompositeKeyValues
{
get
{
if (RequiresDeserialization)
{
DeserializeMembers();
}
return _compositeKeyValues;
}
}
///
/// Gets the entity set for this entity key from the given metadata workspace, by
/// entity container name and entity set name.
///
/// workspace in which to look up the entity set
/// the entity set from the given workspace for this entity key
/// the entity set could not be located in the workspace
public EntitySet GetEntitySet(MetadataWorkspace metadataWorkspace)
{
EntityUtil.CheckArgumentNull(metadataWorkspace, "metadataWorkspace");
if (String.IsNullOrEmpty(_entityContainerName) || String.IsNullOrEmpty(_entitySetName))
{
throw EntityUtil.MissingQualifiedEntitySetName();
}
// GetEntityContainer will throw if it cannot find the container
// SQLBUDT 479443: If this entity key was initially created using an entity set
// from a different workspace, look up the entity set in the new workspace.
// Metadata will throw an ArgumentException if the entity set could not be found.
return metadataWorkspace
.GetEntityContainer(_entityContainerName, DataSpace.CSpace)
.GetEntitySetByName(_entitySetName, false);
}
#region Equality/Hashing
///
/// Compares this instance to a given key by their values.
///
/// the key to compare against this instance
/// true if this instance is equal to the given key, and false otherwise
public override bool Equals(object obj)
{
return InternalEquals(this, obj as EntityKey);
}
///
/// Compares this instance to a given key by their values.
///
/// the key to compare against this instance
/// true if this instance is equal to the given key, and false otherwise
public bool Equals(EntityKey other)
{
return InternalEquals(this, other);
}
///
/// Returns a value-based hash code, to allow EntityKey to be used in hash tables.
///
/// the hash value of this EntityKey
public override int GetHashCode()
{
int hashCode = _hashCode;
if (0 == hashCode)
{
if (RequiresDeserialization)
{
DeserializeMembers();
}
int baseHash = 0;
if (_entitySetName != null)
{
baseHash += _entitySetName.GetHashCode();
}
if (_entityContainerName != null)
{
baseHash += _entityContainerName.GetHashCode();
}
// If the key is not temporary, determine a hash code based on the value(s) within the key.
if (null != _singletonKeyValue)
{
hashCode = unchecked(baseHash ^ _singletonKeyValue.GetHashCode());
}
else if (null != _compositeKeyValues)
{
hashCode = unchecked(baseHash ^ GetHashCode(_compositeKeyValues));
}
else
{
// If the key is temporary, use default hash code
hashCode = base.GetHashCode();
}
// cache the hash code if we are a locked or fully specified EntityKey
if (_isLocked || (!String.IsNullOrEmpty(_entitySetName) &&
!String.IsNullOrEmpty(_entityContainerName) &&
(_singletonKeyValue != null || _compositeKeyValues != null)))
{
_hashCode = hashCode;
}
}
return hashCode;
}
private static int GetHashCode(object[] compositeKeyValues)
{
int hashCode = 0;
for(int i = 0; i < compositeKeyValues.Length; ++i)
{
// For a composite key, chain-XOR each value to compute the hash code.
// This implies that the keys for the following two objects (from the
// same entity set) will hash to the same value, despite being unequal.
// This isn't a bug, just a known hash collision.
// Object1:
// KeyField1 = 10;
// KeyField2 = 20;
// Object2:
// KeyField1 = 20;
// KeyField2 = 10;
unchecked {
hashCode += compositeKeyValues[i].GetHashCode();
}
}
return hashCode;
}
///
/// Compares two keys by their values.
///
/// a key to compare
/// a key to compare
/// true if the two keys are equal, false otherwise
public static bool operator ==(EntityKey key1, EntityKey key2)
{
#if DEBUG
if (((object)NoEntitySetKey == (object)key1) || ((object)EntityNotValidKey == (object)key1) ||
((object)NoEntitySetKey == (object)key2) || ((object)EntityNotValidKey == (object)key1)
// || (null==(object)key1) || (null==(object)key2)) //To check for internal use of null==key
)
{
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()");
}
#endif
return InternalEquals(key1, key2);
}
///
/// Compares two keys by their values.
///
/// a key to compare
/// a key to compare
/// true if the two keys are not equal, false otherwise
public static bool operator !=(EntityKey key1, EntityKey key2)
{
#if DEBUG
if (((object)NoEntitySetKey == (object)key1) || ((object)EntityNotValidKey == (object)key1) ||
((object)NoEntitySetKey == (object)key2) || ((object)EntityNotValidKey == (object)key1))
// || (null==(object)key1) || (null==(object)key2)) //To check for internal use of null==key
{
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()");
}
#endif
return !InternalEquals(key1, key2);
}
///
/// Internal function to compare two keys by their values.
///
/// a key to compare
/// a key to compare
/// true if the two keys are equal, false otherwise
private static bool InternalEquals(EntityKey key1, EntityKey key2)
{
// If both are null or refer to the same object, they're equal.
if (object.ReferenceEquals(key1, key2))
{
return true;
}
// If exactly one is null (avoid calling EntityKey == operator overload), they're not equal.
if (object.ReferenceEquals(key1, null) || object.ReferenceEquals(key2, null))
{
return false;
}
if (key1.RequiresDeserialization)
{
key1.DeserializeMembers();
}
else if (key2.RequiresDeserialization)
{
key2.DeserializeMembers();
}
// If the hash codes differ, the keys are not equal. Note that
// a key's hash code is cached after being computed for the first time,
// so this check will only incur the cost of computing a hash code
// at most once for a given key.
// The primary caller is Dictionary
// at which point Equals is only called after HashCode was deteremined to be equal
//if (key1.GetHashCode() != key2.GetHashCode())
//{
// return false;
//}
if (null != key1._singletonKeyValue)
{
// Compare the single value (if the second is null, false should be returned)
return (key1._singletonKeyValue.Equals(key2._singletonKeyValue) &&
String.Equals(key1._keyNames[0], key2._keyNames[0]) &&
String.Equals(key1._entityContainerName, key2._entityContainerName) &&
String.Equals(key1._entitySetName, key2._entitySetName));
}
// If either key is temporary, they're not equal. This is because
// temporary keys are compared by CLR reference, and we've already
// checked reference equality.
// If the first key is a composite key and the second one isn't, they're not equal.
// They're both composite keys, so compare each value.
return ((null != key1._compositeKeyValues) &&
(null != key2._compositeKeyValues) &&
String.Equals(key1._entityContainerName, key2._entityContainerName) &&
String.Equals(key1._entitySetName, key2._entitySetName) &&
(key1._compositeKeyValues.Length == key2._compositeKeyValues.Length) &&
ComplexKeyValuesEqual(key1, key2));
}
private static bool ComplexKeyValuesEqual(EntityKey key1, EntityKey key2)
{
for (int i = 0; i < key1._compositeKeyValues.Length; ++i)
{
if (key1._keyNames[i].Equals(key2._keyNames[i]))
{
if (!Object.Equals(key1._compositeKeyValues[i], key2._compositeKeyValues[i]))
{
return false;
}
}
else
{
// Key names might not be in the same order so try a slower approach that matches
// key names between the keys.
return KeyValuesEqual(key1, key2);
}
}
return true;
}
private static bool KeyValuesEqual(EntityKey key1, EntityKey key2)
{
Dictionary values = new Dictionary();
foreach (EntityKeyMember key1Value in key1.EntityKeyValues)
{
values.Add(key1Value.Key, key1Value.Value);
}
foreach (EntityKeyMember key2Value in key2.EntityKeyValues)
{
object value;
if (!values.TryGetValue(key2Value.Key, out value) || !object.Equals(key2Value.Value, value))
{
return false;
}
values.Remove(key2Value.Key);
}
return true;
}
#endregion
///
/// Returns an array of string/ pairs, one for each key value in this EntityKey,
/// where the string is the key member name and the DbExpression is the value in this EntityKey
/// for that key member, represented as a with the same result
/// type as the key member.
///
/// The command tree to use to create the key value constant expressions
/// The entity set to which this EntityKey refers; used to verify that this key has the required key members
/// The name -> expression mappings for the key member values represented by this EntityKey
internal KeyValuePair[] GetKeyValueExpressions(DbCommandTree tree, EntitySet entitySet)
{
Debug.Assert(!IsTemporary, "GetKeyValueExpressions doesn't make sense for temporary keys - they have no values.");
Debug.Assert(entitySet != null, "GetEntitySet should not return null.");
Debug.Assert(entitySet.Name == _entitySetName, "EntitySet returned from GetEntitySet has incorrect name.");
int numKeyMembers = 0;
if (!IsTemporary)
{
if (_singletonKeyValue != null)
{
numKeyMembers = 1;
}
else
{
numKeyMembers = _compositeKeyValues.Length;
}
}
if (((EntitySetBase)entitySet).ElementType.KeyMembers.Count != numKeyMembers)
{
// If we found an entity set by name that's a different CLR reference
// than the one contained by this EntityKey, the two entity sets could
// be incompatible. The only error case we need to handle here is the
// one where the number of key members differs; other error cases
// will be handled by the command tree builder methods.
//
throw EntityUtil.EntitySetDoesNotMatch("metadataWorkspace", TypeHelpers.GetFullName(entitySet));
}
// Iterate over the internal collection of string->object
// key value pairs and create a list of string->constant
// expression key value pairs.
KeyValuePair[] keyColumns;
if (_singletonKeyValue != null)
{
EdmMember singletonKeyMember = ((EntitySetBase)entitySet).ElementType.KeyMembers[0];
Debug.Assert(singletonKeyMember != null, "Metadata for singleton key member shouldn't be null.");
keyColumns = new KeyValuePair[] {
new KeyValuePair(singletonKeyMember.Name,
tree.CreateConstantExpression(_singletonKeyValue, Helper.GetModelTypeUsage(singletonKeyMember)))
};
}
else
{
keyColumns = new KeyValuePair[_compositeKeyValues.Length];
for (int i = 0; i < _compositeKeyValues.Length; ++i)
{
Debug.Assert(_compositeKeyValues[i] != null, "Values within key-value pairs cannot be null.");
EdmMember keyMember = ((EntitySetBase)entitySet).ElementType.KeyMembers[i];
Debug.Assert(keyMember != null, "Metadata for key members shouldn't be null.");
keyColumns[i] = new KeyValuePair(keyMember.Name,
tree.CreateConstantExpression(_compositeKeyValues[i], Helper.GetModelTypeUsage(keyMember)));
}
}
return keyColumns;
}
///
/// Returns a string representation of this EntityKey, for use in debugging.
/// Note that the returned string contains potentially sensitive information
/// (i.e., key values), and thus shouldn't be publicly exposed.
///
internal string ConcatKeyValue()
{
System.Text.StringBuilder builder = new System.Text.StringBuilder();
builder.Append("EntitySet=").Append(_entitySetName);
if (!IsTemporary)
{
foreach (EntityKeyMember pair in EntityKeyValues)
{
builder.Append(';');
builder.Append(pair.Key).Append("=").Append(pair.Value);
}
}
return builder.ToString();
}
///
/// Returns the appropriate value for the given key name.
///
internal object FindValueByName(string keyName)
{
Debug.Assert(!IsTemporary, "FindValueByName should not be called for temporary keys.");
if (SingletonKeyValue != null)
{
Debug.Assert(_keyNames[0] == keyName, "For a singleton key, the given keyName must match.");
return _singletonKeyValue;
}
else
{
object[] compositeKeyValues = CompositeKeyValues;
for (int i = 0; i < compositeKeyValues.Length; i++)
{
if (keyName == _keyNames[i])
{
return compositeKeyValues[i];
}
}
throw EntityUtil.ArgumentOutOfRange("keyName");
}
}
internal static void GetEntitySetName(string qualifiedEntitySetName, out string entitySet, out string container)
{
entitySet = null;
container = null;
EntityUtil.CheckStringArgument(qualifiedEntitySetName, "qualifiedEntitySetName");
string[] result = qualifiedEntitySetName.Split('.');
if (result.Length != 2)
{
throw EntityUtil.InvalidQualifiedEntitySetName();
}
container = result[0];
entitySet = result[1];
// both parts must be non-empty
if (container == null || container.Length == 0 ||
entitySet == null || entitySet.Length == 0)
{
throw EntityUtil.InvalidQualifiedEntitySetName();
}
ValidateName(container);
ValidateName(entitySet);
}
internal static void ValidateName(string name)
{
if (!System.Data.EntityModel.SchemaObjectModel.Utils.ValidUndottedName(name))
{
throw EntityUtil.EntityKeyInvalidName(name);
}
}
#region Key Value Assignment and Validation
private static void CheckKeyValues(EntitySet entitySet, IEnumerable> entityKeyValues,
out string[] keyNames, out object singletonKeyValue, out object[] compositeKeyValues)
{
Debug.Assert(entitySet != null, "EntitySet should not be null.");
EntityUtil.CheckArgumentNull(entityKeyValues, "entityKeyValues");
int numExpectedKeyValues = 0;
int numActualKeyValues = 0;
ReadOnlyMetadataCollection keyMembers = null;
singletonKeyValue = null;
compositeKeyValues = null;
// Determine if we're a single or composite key.
foreach (KeyValuePair value in entityKeyValues)
{
numActualKeyValues++;
}
keyMembers = ((EntitySetBase)entitySet).ElementType.KeyMembers;
keyNames = ((EntitySetBase)entitySet).ElementType.KeyMemberNames;
numExpectedKeyValues = keyMembers.Count;
Debug.Assert(numExpectedKeyValues > 0, "Metadata returned an invalid number of keys.");
if (numExpectedKeyValues != numActualKeyValues)
{
throw EntityUtil.IncorrectNumberOfKeyValuePairs("entityKeyValues", entitySet.ElementType.FullName, numExpectedKeyValues, numActualKeyValues);
}
if (numExpectedKeyValues == 1)
{
foreach (KeyValuePair keyValuePair in entityKeyValues)
{
if (EntityUtil.IsNull(keyValuePair.Value) || String.IsNullOrEmpty(keyValuePair.Key))
{
throw EntityUtil.NoNullsAllowedInKeyValuePairs("entityKeyValues");
}
if (keyNames[0] != keyValuePair.Key)
{ // Validate key name
Debug.Assert(null != entitySet, "null entityset");
throw EntityUtil.MissingKeyValue("entityKeyValues", keyNames[0], entitySet.ElementType.FullName);
}
singletonKeyValue = keyValuePair.Value;
// Validate type of key value
Type entitySetKeyType = ((PrimitiveType)keyMembers[0].TypeUsage.EdmType).ClrEquivalentType;
if (entitySetKeyType != singletonKeyValue.GetType())
{
throw EntityUtil.IncorrectValueType("entityKeyValues", keyMembers[0].Name, entitySetKeyType.FullName, singletonKeyValue.GetType().FullName);
}
}
}
else
{
compositeKeyValues = new object[numExpectedKeyValues];
int i = 0;
for (i = 0; i < numExpectedKeyValues; ++i)
{
EdmMember keyField = ((EntitySetBase)entitySet).ElementType.KeyMembers[i];
bool foundMember = false;
foreach (KeyValuePair keyValuePair in entityKeyValues)
{
if (keyField.Name == keyValuePair.Key)
{
if (EntityUtil.IsNull(keyValuePair.Value) || String.IsNullOrEmpty(keyValuePair.Key))
{
throw EntityUtil.NoNullsAllowedInKeyValuePairs("entityKeyValues");
}
compositeKeyValues[i] = keyValuePair.Value;
foundMember = true;
// Validate type of key values
Type entitySetKeyType = ((PrimitiveType)keyField.TypeUsage.EdmType).ClrEquivalentType;
if (entitySetKeyType != compositeKeyValues[i].GetType())
{
throw EntityUtil.IncorrectValueType("entityKeyValues", keyField.Name, entitySetKeyType.FullName, compositeKeyValues[i].GetType().FullName);
}
break;
}
}
// Validate Key Name (if we found it or not)
if (!foundMember)
{
throw EntityUtil.MissingKeyValue("entityKeyValues", keyField.Name, entitySet.ElementType.FullName);
}
}
}
}
private static bool CheckKeyValues(IEnumerable> entityKeyValues,
out string[] keyNames, out object singletonKeyValue, out object[] compositeKeyValues)
{
return CheckKeyValues(entityKeyValues, false, false, out keyNames, out singletonKeyValue, out compositeKeyValues);
}
private static bool CheckKeyValues(IEnumerable> entityKeyValues, bool allowNullKeys, bool tokenizeStrings,
out string[] keyNames, out object singletonKeyValue, out object[] compositeKeyValues)
{
EntityUtil.CheckArgumentNull(entityKeyValues, "entityKeyValues");
int numExpectedKeyValues;
int numActualKeyValues = 0;
keyNames = null;
singletonKeyValue = null;
compositeKeyValues = null;
// Determine if we're a single or composite key.
foreach (KeyValuePair value in entityKeyValues)
{
numActualKeyValues++;
}
numExpectedKeyValues = numActualKeyValues;
if (numExpectedKeyValues == 0)
{
if (!allowNullKeys)
{
throw EntityUtil.EntityKeyMustHaveValues("entityKeyValues");
}
}
else
{
keyNames = new string[numExpectedKeyValues];
if (numExpectedKeyValues == 1)
{
lock (_nameLookup)
{
foreach (KeyValuePair keyValuePair in entityKeyValues)
{
if (EntityUtil.IsNull(keyValuePair.Value) || String.IsNullOrEmpty(keyValuePair.Key))
{
throw EntityUtil.NoNullsAllowedInKeyValuePairs("entityKeyValues");
}
ValidateName(keyValuePair.Key);
keyNames[0] = tokenizeStrings ? EntityKey.LookupSingletonName(keyValuePair.Key) : keyValuePair.Key;
singletonKeyValue = keyValuePair.Value;
}
}
}
else
{
compositeKeyValues = new object[numExpectedKeyValues];
int i = 0;
lock (_nameLookup)
{
foreach (KeyValuePair keyValuePair in entityKeyValues)
{
if (EntityUtil.IsNull(keyValuePair.Value) || String.IsNullOrEmpty(keyValuePair.Key))
{
throw EntityUtil.NoNullsAllowedInKeyValuePairs("entityKeyValues");
}
Debug.Assert(null == keyNames[i], "shouldn't have a name yet");
ValidateName(keyValuePair.Key);
keyNames[i] = tokenizeStrings ? EntityKey.LookupSingletonName(keyValuePair.Key) : keyValuePair.Key;
compositeKeyValues[i] = keyValuePair.Value;
i++;
}
}
}
}
return numExpectedKeyValues > 0;
}
///
/// Validates the record parameter passed to the EntityKey constructor,
/// and converts the data into the form required by EntityKey. For singleton keys,
/// this is a single object. For composite keys, this is an object array.
///
/// the entity set metadata object which this key refers to
/// the parameter to validate
/// the number of expected key-value pairs
/// the name of the argument to use in exception messages
/// the validated value(s) (for a composite key, an object array is returned)
private static void CheckKeyValues(EntitySet entitySet, IExtendedDataRecord record,
out string[] keyNames, out object singletonKeyValue, out object[] compositeKeyValues)
{
singletonKeyValue = null;
compositeKeyValues = null;
int numExpectedKeyValues = ((EntitySetBase)entitySet).ElementType.KeyMembers.Count;
keyNames = ((EntitySetBase)entitySet).ElementType.KeyMemberNames;
EntityType entityType = record.DataRecordInfo.RecordType.EdmType as EntityType;
if (entityType == null)
{
throw EntityUtil.DataRecordMustBeEntity();
}
// ensure the type contained by this entity set matches the type contained by the data record
if (entitySet != null && !entitySet.ElementType.IsAssignableFrom(entityType))
{
throw EntityUtil.EntityTypesDoNotMatch(entityType.Name, entitySet.ElementType.FullName);
}
Debug.Assert(numExpectedKeyValues > 0, "Should be expecting a positive number of key-values.");
if (numExpectedKeyValues == 1)
{
// Optimize for a singleton key.
EdmMember member = entityType.KeyMembers[0];
try
{
singletonKeyValue = CheckValue("record", member.Name, record[member.Name], (PrimitiveType)member.TypeUsage.EdmType);
}
catch (IndexOutOfRangeException ex)
{
throw EntityUtil.MissingKeyValue("record", member.Name, entityType.FullName, ex);
}
}
else
{
compositeKeyValues = new object[numExpectedKeyValues];
// grab each key-field from the data record
for (int i = 0; i < numExpectedKeyValues; ++i)
{
EdmMember member = entityType.KeyMembers[i];
try
{
compositeKeyValues[i] = CheckValue("record", member.Name, record[member.Name],
(PrimitiveType)member.TypeUsage.EdmType);
}
catch (IndexOutOfRangeException ex)
{
throw EntityUtil.MissingKeyValue("record", member.Name, entityType.FullName, ex);
}
}
}
}
///
/// Checks whether the given value is valid for the given key field.
///
/// the name of the argument that contains a value of the given CLR type
/// the name of the key field that contains the value of the given CLR type
/// the value to validate
/// the expected type of the value
/// the validated value
/// the value is null
/// the value is of an incorrect type
private static object CheckValue(string argumentName, string keyFieldName, object value, PrimitiveType expectedType)
{
Debug.Assert(expectedType != null, "expectedType cannot be null");
if (EntityUtil.IsNull(value))
{
throw EntityUtil.NoNullsAllowedInKeyValuePairs(argumentName);
}
if (expectedType.ClrEquivalentType != value.GetType())
{
throw EntityUtil.IncorrectValueType(argumentName, keyFieldName, expectedType.ClrEquivalentType.FullName, value.GetType().FullName);
}
return value;
}
///
/// Verify that the types of the objects passed in to be used as keys actually match the types from the model.
/// This error is also caught when the entity is materialized and when the key value is set, at which time it
/// also throws ThrowSetInvalidValue().
/// SQLBUDT 513838. This error is possible and should be caught at run time, not in an assertion.
///
/// The EntitySet to validate against
internal void ValidateEntityKey(EntitySet entitySet)
{
ValidateEntityKey(entitySet, false, null);
}
///
/// Verify that the types of the objects passed in to be used as keys actually match the types from the model.
/// This error is also caught when the entity is materialized and when the key value is set, at which time it
/// also throws ThrowSetInvalidValue().
/// SQLBUDT 513838. This error is possible and should be caught at run time, not in an assertion.
///
/// The EntitySet to validate against
/// Type of the exception to throw
internal void ValidateEntityKey(EntitySet entitySet, bool isArgumentException, string argumentName)
{
if (entitySet != null)
{
ReadOnlyMetadataCollection keyMembers = ((EntitySetBase)entitySet).ElementType.KeyMembers;
if (_singletonKeyValue != null)
{
// 1. Validate number of keys
if (keyMembers.Count != 1)
{
if (isArgumentException)
{
throw EntityUtil.IncorrectNumberOfKeyValuePairs(argumentName, entitySet.ElementType.FullName, keyMembers.Count, 1);
}
else
{
throw EntityUtil.IncorrectNumberOfKeyValuePairsInvalidOperation(entitySet.ElementType.FullName, keyMembers.Count, 1);
}
}
// 2. Validate type of key values
Type entitySetKeyType = ((PrimitiveType)keyMembers[0].TypeUsage.EdmType).ClrEquivalentType;
if (entitySetKeyType != _singletonKeyValue.GetType())
{
if (isArgumentException)
{
throw EntityUtil.IncorrectValueType(argumentName, keyMembers[0].Name, entitySetKeyType.FullName, _singletonKeyValue.GetType().FullName);
}
else
{
throw EntityUtil.IncorrectValueTypeInvalidOperation(keyMembers[0].Name, entitySetKeyType.FullName, _singletonKeyValue.GetType().FullName);
}
}
// 3. Validate key names
if (_keyNames[0] != keyMembers[0].Name)
{
if (isArgumentException)
{
throw EntityUtil.MissingKeyValue(argumentName, keyMembers[0].Name, entitySet.ElementType.FullName);
}
else
{
throw EntityUtil.MissingKeyValueInvalidOperation(keyMembers[0].Name, entitySet.ElementType.FullName);
}
}
}
else if (null != _compositeKeyValues)
{
// 1. Validate number of keys
if (keyMembers.Count != _compositeKeyValues.Length)
{
if (isArgumentException)
{
throw EntityUtil.IncorrectNumberOfKeyValuePairs(argumentName, entitySet.ElementType.FullName, keyMembers.Count, _compositeKeyValues.Length);
}
else
{
throw EntityUtil.IncorrectNumberOfKeyValuePairsInvalidOperation(entitySet.ElementType.FullName, keyMembers.Count, _compositeKeyValues.Length);
}
}
for (int i = 0; i < _compositeKeyValues.Length; ++i)
{
EdmMember keyField = ((EntitySetBase)entitySet).ElementType.KeyMembers[i];
bool foundMember = false;
for(int j = 0; j < _compositeKeyValues.Length; ++j)
{
if (keyField.Name == _keyNames[j])
{
// 2. Validate type of key values
Type entitySetKeyType = ((PrimitiveType)keyField.TypeUsage.EdmType).ClrEquivalentType;
if (entitySetKeyType != _compositeKeyValues[j].GetType())
{
if (isArgumentException)
{
throw EntityUtil.IncorrectValueType(argumentName, keyField.Name, entitySetKeyType.FullName, _compositeKeyValues[j].GetType().FullName);
}
else
{
throw EntityUtil.IncorrectValueTypeInvalidOperation(keyField.Name, entitySetKeyType.FullName, _compositeKeyValues[j].GetType().FullName);
}
}
foundMember = true;
break;
}
}
// 3. Validate Key Name (if we found it or not)
if (!foundMember)
{
if (isArgumentException)
{
throw EntityUtil.MissingKeyValue(argumentName, keyField.Name, entitySet.ElementType.FullName);
}
else
{
throw EntityUtil.MissingKeyValueInvalidOperation(keyField.Name, entitySet.ElementType.FullName);
}
}
}
}
}
}
///
/// Asserts that the "state" of the EntityKey is correct, by validating assumptions
/// based on whether the key is a singleton, composite, or temporary.
///
/// whether we expect this EntityKey to be marked temporary
[Conditional("DEBUG")]
private void AssertCorrectState(EntitySetBase entitySet, bool isTemporary)
{
if (_singletonKeyValue != null)
{
Debug.Assert(!isTemporary, "Singleton keys should not be expected to be temporary.");
Debug.Assert(_compositeKeyValues == null, "The EntityKey is marked as both a singleton key and a composite key - this is illegal.");
if (entitySet != null)
{
Debug.Assert(entitySet.ElementType.KeyMembers.Count == 1, "For a singleton key, the number of key fields must be exactly 1.");
}
}
else if (_compositeKeyValues != null)
{
Debug.Assert(!isTemporary, "Composite keys should not be expected to be temporary.");
if (entitySet != null)
{
Debug.Assert(entitySet.ElementType.KeyMembers.Count > 1, "For a compsite key, the number of key fields should be greater than 1.");
Debug.Assert(entitySet.ElementType.KeyMembers.Count == _compositeKeyValues.Length, "Incorrect number of values specified to composite key.");
}
for (int i = 0; i < _compositeKeyValues.Length; ++i)
{
Debug.Assert(_compositeKeyValues[i] != null, "Values passed to a composite EntityKey cannot be null.");
}
}
else if (!IsTemporary)
{
// one of our static keys
Debug.Assert(!isTemporary, "Static keys should not be expected to be temporary.");
Debug.Assert(this.EntityKeyValues == null, "The EntityKeyValues property for Static EntityKeys must return null.");
Debug.Assert(this.EntityContainerName == null, "The EntityContainerName property for Static EntityKeys must return null.");
Debug.Assert(this.EntitySetName != null, "The EntitySetName property for Static EntityKeys must not return null.");
}
else
{
Debug.Assert(isTemporary, "The EntityKey is marked as neither a singleton or composite. Therefore, it should be expected to be temporary.");
Debug.Assert(this.IsTemporary, "The EntityKey is marked as neither a singleton or composite. Therefore it must be marked as temporary.");
Debug.Assert(this.EntityKeyValues == null, "The EntityKeyValues property for temporary EntityKeys must return null.");
}
}
#endregion
#region Serialization
///
///
///
///
[OnDeserializing]
public void OnDeserializing(StreamingContext context)
{
if (RequiresDeserialization)
{
DeserializeMembers();
}
}
///
///
///
///
[OnDeserialized]
public void OnDeserialized(StreamingContext context)
{
lock (_nameLookup)
{
_entitySetName = LookupSingletonName(_entitySetName);
_entityContainerName = LookupSingletonName(_entityContainerName);
if (_keyNames != null)
{
for (int i = 0; i < _keyNames.Length; i++)
{
_keyNames[i] = LookupSingletonName(_keyNames[i]);
}
}
}
}
///
/// Dev Note: this must be called from within a _lock block on _nameLookup
///
///
///
private static string LookupSingletonName(string name)
{
if (String.IsNullOrEmpty(name))
{
return null;
}
if (_nameLookup.ContainsKey(name))
{
return _nameLookup[name];
}
_nameLookup.Add(name, name);
return name;
}
private void ValidateWritable(object instance)
{
if (_isLocked || instance != null)
{
throw EntityUtil.CannotChangeEntityKey();
}
}
private bool RequiresDeserialization
{
get { return _deserializedMembers != null; }
}
private void DeserializeMembers()
{
if (CheckKeyValues(new KeyValueReader(_deserializedMembers), true, true, out _keyNames, out _singletonKeyValue, out _compositeKeyValues))
{
// If we received values from the _deserializedMembers, then we do not need to track these any more
_deserializedMembers = null;
}
}
#endregion
private class KeyValueReader : IEnumerable>
{
IEnumerable _enumerator;
public KeyValueReader(IEnumerable enumerator)
{
_enumerator = enumerator;
}
#region IEnumerable> Members
public IEnumerator> GetEnumerator()
{
foreach (EntityKeyMember pair in _enumerator)
{
if (pair != null)
{
yield return new KeyValuePair(pair.Key, pair.Value);
}
}
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
#endregion
}
}
///
/// Information about a key that is part of an EntityKey.
/// A key member contains the key name and value.
///
[DataContract]
[Serializable]
public class EntityKeyMember
{
private string _keyName;
private object _keyValue;
///
/// Creates an empty EntityKeyMember. This constructor is used by serialization.
///
public EntityKeyMember()
{
}
///
/// Creates a new EntityKeyMember with the specified key name and value.
///
/// The key name
/// The key value
public EntityKeyMember(string keyName, object keyValue)
{
EntityUtil.CheckArgumentNull(keyName, "keyName");
EntityUtil.CheckArgumentNull(keyValue, "keyValue");
_keyName = keyName;
_keyValue = keyValue;
}
///
/// The key name
///
[DataMember]
public string Key
{
get
{
return _keyName;
}
set
{
ValidateWritable(_keyName);
EntityUtil.CheckArgumentNull(value, "value");
_keyName = value;
}
}
///
/// The key value
///
[DataMember]
public object Value
{
get
{
return _keyValue;
}
set
{
ValidateWritable(_keyValue);
EntityUtil.CheckArgumentNull(value, "value");
_keyValue = value;
}
}
///
/// Returns a string representation of the EntityKeyMember
///
/// A string representation of the EntityKeyMember
public override string ToString()
{
return String.Format(System.Globalization.CultureInfo.CurrentCulture, "[{0}, {1}]", _keyName, _keyValue);
}
///
/// Ensures that the instance can be written to (value must be null)
///
private void ValidateWritable(object instance)
{
if (instance != null)
{
throw EntityUtil.CannotChangeEntityKey();
}
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Diagnostics;
using System.Collections;
using System.Data.Common;
using System.Data.Common.Utils;
using System.Data.Metadata.Edm;
using System.Data.Mapping;
using System.Data.Objects;
using System.Data.Common.CommandTrees;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using System.Xml;
namespace System.Data
{
///
/// An identifier for an entity.
///
[DebuggerDisplay("{ConcatKeyValue()}")]
[Serializable]
[DataContract(IsReference=true)]
public sealed class EntityKey : IEquatable
{
// The implementation of EntityKey is optimized for the following common cases:
// 1) Keys constructed internally rather by the user - in particular, keys
// created by the bridge on the round-trip from query.
// 2) Single-valued (as opposed to composite) keys.
// We accomplish this by maintaining two variables, at most one of which is non-null.
// The first is of type object and in the case of a singleton key, is set to the
// single key value. The second is an object array and in the case of
// a composite key, is set to the list of key values. If both variables are null,
// the EntityKey is a temporary key. Note that the key field names
// are not stored - for composite keys, the values are stored in the order in which
// metadata reports the corresponding key members.
// The following 5 fields are serialized. Adding or removing a serialized field is considered
// a breaking change. This includes changing the field type or field name of existing
// serialized fields. If you need to make this kind of change, it may be possible, but it
// will require some custom serialization/deserialization code.
private string _entitySetName;
private string _entityContainerName;
private object _singletonKeyValue; // non-null for singleton keys
private object[] _compositeKeyValues; // non-null for composite keys
private string[] _keyNames; // key names that correspond to the key values
private bool _isLocked; // determines if this key is lock from writing
[NonSerialized]
private EntityKeyMember[] _deserializedMembers;
// The hash code is not serialized since it can be computed differently on the deserialized system.
[NonSerialized]
private int _hashCode; // computed as needed
// Names for constant EntityKeys
private const string s_NoEntitySetKey = "NoEntitySetKey.NoEntitySetKey";
private const string s_EntityNotValidKey = "EntityNotValidKey.EntityNotValidKey";
///
/// A singleton EntityKey by which a readonly entity is identified.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] // Justification: these are internal so they cannot be modified publically
public static readonly EntityKey NoEntitySetKey = new EntityKey(s_NoEntitySetKey);
///
/// A singleton EntityKey identifying an entity resulted from a failed TREAT.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] // Justification: these are internal so they cannot be modified publically
public static readonly EntityKey EntityNotValidKey = new EntityKey(s_EntityNotValidKey);
///
/// A dictionary of names so that singleton instances of names can be used
///
private static Dictionary _nameLookup = new Dictionary();
#region Public Constructors
///
/// Constructs an empty EntityKey. For use during XmlSerialization.
///
public EntityKey()
{
_isLocked = false;
}
///
/// Constructs an EntityKey with the given key values.
///
/// The EntitySet name, qualified by the EntityContainer name, of the entity
/// The key-value pairs that identify the entity
public EntityKey(string qualifiedEntitySetName, IEnumerable> entityKeyValues)
{
GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
CheckKeyValues(entityKeyValues, out _keyNames, out _singletonKeyValue, out _compositeKeyValues);
AssertCorrectState(null, false);
_isLocked = true;
}
///
/// Constructs an EntityKey with the given key values.
///
/// The EntitySet name, qualified by the EntityContainer name, of the entity
/// The key-value pairs that identify the entity
public EntityKey(string qualifiedEntitySetName, IEnumerable entityKeyValues)
{
GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
CheckKeyValues(new KeyValueReader(entityKeyValues), out _keyNames, out _singletonKeyValue, out _compositeKeyValues);
AssertCorrectState(null, false);
_isLocked = true;
}
///
/// Constructs an EntityKey with the given single key name and value.
///
/// The EntitySet name, qualified by the EntityContainer name, of the entity
/// The key name that identifies the entity
/// The key value that identifies the entity
public EntityKey(string qualifiedEntitySetName, string keyName, object keyValue)
{
GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
EntityUtil.CheckStringArgument(keyName, "keyName");
EntityUtil.CheckArgumentNull(keyValue, "keyValue");
_keyNames = new string[1];
ValidateName(keyName);
_keyNames[0] = keyName;
_singletonKeyValue = keyValue;
AssertCorrectState(null, false);
_isLocked = true;
}
#endregion
#region Internal Constructors
///
/// Constructs an EntityKey from an IExtendedDataRecord representing the entity.
///
/// EntitySet of the entity
/// an IExtendedDataRecord that represents the entity
internal EntityKey(EntitySet entitySet, IExtendedDataRecord record)
{
EntityUtil.CheckArgumentNull(entitySet, "entitySet");
EntityUtil.CheckArgumentNull(entitySet.Name, "entitySet.Name");
EntityUtil.CheckArgumentNull(entitySet.EntityContainer, "entitySet.Container");
EntityUtil.CheckArgumentNull(entitySet.EntityContainer.Name, "entitySet.Container.Name");
EntityUtil.CheckArgumentNull(record, "record");
_entitySetName = entitySet.Name;
_entityContainerName = entitySet.EntityContainer.Name;
CheckKeyValues(entitySet, record, out _keyNames, out _singletonKeyValue, out _compositeKeyValues);
AssertCorrectState(entitySet, false);
_isLocked = true;
}
///
/// Constructs an EntityKey from an IExtendedDataRecord representing the entity.
///
/// EntitySet of the entity
/// an IExtendedDataRecord that represents the entity
internal EntityKey(string qualifiedEntitySetName)
{
GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
_isLocked = true;
}
///
/// Constructs a temporary EntityKey with the given EntitySet.
/// Temporary keys do not store key field names
///
/// EntitySet of the entity
internal EntityKey(EntitySetBase entitySet)
{
EntityUtil.CheckArgumentNull(entitySet, "entitySet");
Debug.Assert(entitySet.EntityContainer != null, "EntitySet.EntityContainer cannot be null.");
_entitySetName = entitySet.Name;
_entityContainerName = entitySet.EntityContainer.Name;
AssertCorrectState(entitySet, true);
_isLocked = true;
}
///
/// Constructor optimized for a singleton key.
/// SQLBUDT 478655: Performance optimization: Does no integrity checking on the key value.
/// SQLBUDT 523554: Performance optimization: Does no validate type of key members.
///
/// EntitySet of the entity
/// The single value that composes the entity's key, assumed to contain the correct type.
internal EntityKey(EntitySetBase entitySet, object singletonKeyValue)
{
Debug.Assert(entitySet != null, "EntitySet cannot be null.");
Debug.Assert(entitySet.EntityContainer != null, "EntitySet.EntityContainer cannot be null.");
Debug.Assert(singletonKeyValue != null, "Singleton key value cannot be null.");
_singletonKeyValue = singletonKeyValue;
_entitySetName = entitySet.Name;
_entityContainerName = entitySet.EntityContainer.Name;
_keyNames = entitySet.ElementType.KeyMemberNames; // using EntitySetBase avoids an (EntityType) cast that EntitySet encoure
AssertCorrectState(entitySet, false);
_isLocked = true;
}
///
/// Constructor optimized for a composite key.
/// SQLBUDT 478655: Performance optimization: Does no integrity checking on the key values.
/// SQLBUDT 523554: Performance optimization: Does no validate type of key members.
///
/// EntitySet of the entity
/// A list of the values (at least 2) that compose the entity's key, assumed to contain correct types.
internal EntityKey(EntitySetBase entitySet, object[] compositeKeyValues)
{
Debug.Assert(entitySet != null, "EntitySet cannot be null.");
Debug.Assert(entitySet.EntityContainer != null, "EntitySet.EntityContainer cannot be null.");
Debug.Assert(compositeKeyValues != null, "Composite key values cannot be null.");
_compositeKeyValues = compositeKeyValues;
_entitySetName = entitySet.Name;
_entityContainerName = entitySet.EntityContainer.Name;
_keyNames = entitySet.ElementType.KeyMemberNames; // using EntitySetBase avoids an (EntityType) cast that EntitySet encoure
AssertCorrectState(entitySet, false);
_isLocked = true;
}
#endregion
///
/// Gets the EntitySet name identifying the entity set that contains the entity.
///
[DataMember]
public string EntitySetName
{
get
{
return _entitySetName;
}
set
{
ValidateWritable(_entitySetName);
lock(_nameLookup)
{
_entitySetName = EntityKey.LookupSingletonName(value);
}
}
}
///
/// Gets the EntityContainer name identifying the entity container that contains the entity.
///
[DataMember]
public string EntityContainerName
{
get
{
return _entityContainerName;
}
set
{
ValidateWritable(_entityContainerName);
lock (_nameLookup)
{
_entityContainerName = EntityKey.LookupSingletonName(value);
}
}
}
///
/// Gets the key values that identify the entity.
///
[DataMember]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification="Required for this feature")]
public EntityKeyMember[] EntityKeyValues
{
get
{
if(!IsTemporary)
{
EntityKeyMember[] keyValues;
if (_singletonKeyValue != null)
{
keyValues = new EntityKeyMember[] {
new EntityKeyMember(_keyNames[0], _singletonKeyValue) };
}
else
{
keyValues = new EntityKeyMember[_compositeKeyValues.Length];
for (int i = 0; i < _compositeKeyValues.Length; ++i)
{
keyValues[i] = new EntityKeyMember(_keyNames[i], _compositeKeyValues[i]);
}
}
return keyValues;
}
else
{
return null;
}
}
set
{
ValidateWritable(_keyNames);
if (value != null)
{
if (!CheckKeyValues(new KeyValueReader(value), true, true, out _keyNames, out _singletonKeyValue, out _compositeKeyValues))
{
// If we did not retrieve values from the setter (i.e. encoded settings), we need to keep track of the
// array instance because the array members will be set next.
_deserializedMembers = value;
}
}
}
}
///
/// Gets a value indicating whether this key is a temporary key.
///
public bool IsTemporary
{
get
{
return (SingletonKeyValue == null) && (CompositeKeyValues == null);
}
}
private object SingletonKeyValue
{
get
{
if (RequiresDeserialization)
{
DeserializeMembers();
}
return _singletonKeyValue;
}
}
private object[] CompositeKeyValues
{
get
{
if (RequiresDeserialization)
{
DeserializeMembers();
}
return _compositeKeyValues;
}
}
///
/// Gets the entity set for this entity key from the given metadata workspace, by
/// entity container name and entity set name.
///
/// workspace in which to look up the entity set
/// the entity set from the given workspace for this entity key
/// the entity set could not be located in the workspace
public EntitySet GetEntitySet(MetadataWorkspace metadataWorkspace)
{
EntityUtil.CheckArgumentNull(metadataWorkspace, "metadataWorkspace");
if (String.IsNullOrEmpty(_entityContainerName) || String.IsNullOrEmpty(_entitySetName))
{
throw EntityUtil.MissingQualifiedEntitySetName();
}
// GetEntityContainer will throw if it cannot find the container
// SQLBUDT 479443: If this entity key was initially created using an entity set
// from a different workspace, look up the entity set in the new workspace.
// Metadata will throw an ArgumentException if the entity set could not be found.
return metadataWorkspace
.GetEntityContainer(_entityContainerName, DataSpace.CSpace)
.GetEntitySetByName(_entitySetName, false);
}
#region Equality/Hashing
///
/// Compares this instance to a given key by their values.
///
/// the key to compare against this instance
/// true if this instance is equal to the given key, and false otherwise
public override bool Equals(object obj)
{
return InternalEquals(this, obj as EntityKey);
}
///
/// Compares this instance to a given key by their values.
///
/// the key to compare against this instance
/// true if this instance is equal to the given key, and false otherwise
public bool Equals(EntityKey other)
{
return InternalEquals(this, other);
}
///
/// Returns a value-based hash code, to allow EntityKey to be used in hash tables.
///
/// the hash value of this EntityKey
public override int GetHashCode()
{
int hashCode = _hashCode;
if (0 == hashCode)
{
if (RequiresDeserialization)
{
DeserializeMembers();
}
int baseHash = 0;
if (_entitySetName != null)
{
baseHash += _entitySetName.GetHashCode();
}
if (_entityContainerName != null)
{
baseHash += _entityContainerName.GetHashCode();
}
// If the key is not temporary, determine a hash code based on the value(s) within the key.
if (null != _singletonKeyValue)
{
hashCode = unchecked(baseHash ^ _singletonKeyValue.GetHashCode());
}
else if (null != _compositeKeyValues)
{
hashCode = unchecked(baseHash ^ GetHashCode(_compositeKeyValues));
}
else
{
// If the key is temporary, use default hash code
hashCode = base.GetHashCode();
}
// cache the hash code if we are a locked or fully specified EntityKey
if (_isLocked || (!String.IsNullOrEmpty(_entitySetName) &&
!String.IsNullOrEmpty(_entityContainerName) &&
(_singletonKeyValue != null || _compositeKeyValues != null)))
{
_hashCode = hashCode;
}
}
return hashCode;
}
private static int GetHashCode(object[] compositeKeyValues)
{
int hashCode = 0;
for(int i = 0; i < compositeKeyValues.Length; ++i)
{
// For a composite key, chain-XOR each value to compute the hash code.
// This implies that the keys for the following two objects (from the
// same entity set) will hash to the same value, despite being unequal.
// This isn't a bug, just a known hash collision.
// Object1:
// KeyField1 = 10;
// KeyField2 = 20;
// Object2:
// KeyField1 = 20;
// KeyField2 = 10;
unchecked {
hashCode += compositeKeyValues[i].GetHashCode();
}
}
return hashCode;
}
///
/// Compares two keys by their values.
///
/// a key to compare
/// a key to compare
/// true if the two keys are equal, false otherwise
public static bool operator ==(EntityKey key1, EntityKey key2)
{
#if DEBUG
if (((object)NoEntitySetKey == (object)key1) || ((object)EntityNotValidKey == (object)key1) ||
((object)NoEntitySetKey == (object)key2) || ((object)EntityNotValidKey == (object)key1)
// || (null==(object)key1) || (null==(object)key2)) //To check for internal use of null==key
)
{
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()");
}
#endif
return InternalEquals(key1, key2);
}
///
/// Compares two keys by their values.
///
/// a key to compare
/// a key to compare
/// true if the two keys are not equal, false otherwise
public static bool operator !=(EntityKey key1, EntityKey key2)
{
#if DEBUG
if (((object)NoEntitySetKey == (object)key1) || ((object)EntityNotValidKey == (object)key1) ||
((object)NoEntitySetKey == (object)key2) || ((object)EntityNotValidKey == (object)key1))
// || (null==(object)key1) || (null==(object)key2)) //To check for internal use of null==key
{
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()");
}
#endif
return !InternalEquals(key1, key2);
}
///
/// Internal function to compare two keys by their values.
///
/// a key to compare
/// a key to compare
/// true if the two keys are equal, false otherwise
private static bool InternalEquals(EntityKey key1, EntityKey key2)
{
// If both are null or refer to the same object, they're equal.
if (object.ReferenceEquals(key1, key2))
{
return true;
}
// If exactly one is null (avoid calling EntityKey == operator overload), they're not equal.
if (object.ReferenceEquals(key1, null) || object.ReferenceEquals(key2, null))
{
return false;
}
if (key1.RequiresDeserialization)
{
key1.DeserializeMembers();
}
else if (key2.RequiresDeserialization)
{
key2.DeserializeMembers();
}
// If the hash codes differ, the keys are not equal. Note that
// a key's hash code is cached after being computed for the first time,
// so this check will only incur the cost of computing a hash code
// at most once for a given key.
// The primary caller is Dictionary
// at which point Equals is only called after HashCode was deteremined to be equal
//if (key1.GetHashCode() != key2.GetHashCode())
//{
// return false;
//}
if (null != key1._singletonKeyValue)
{
// Compare the single value (if the second is null, false should be returned)
return (key1._singletonKeyValue.Equals(key2._singletonKeyValue) &&
String.Equals(key1._keyNames[0], key2._keyNames[0]) &&
String.Equals(key1._entityContainerName, key2._entityContainerName) &&
String.Equals(key1._entitySetName, key2._entitySetName));
}
// If either key is temporary, they're not equal. This is because
// temporary keys are compared by CLR reference, and we've already
// checked reference equality.
// If the first key is a composite key and the second one isn't, they're not equal.
// They're both composite keys, so compare each value.
return ((null != key1._compositeKeyValues) &&
(null != key2._compositeKeyValues) &&
String.Equals(key1._entityContainerName, key2._entityContainerName) &&
String.Equals(key1._entitySetName, key2._entitySetName) &&
(key1._compositeKeyValues.Length == key2._compositeKeyValues.Length) &&
ComplexKeyValuesEqual(key1, key2));
}
private static bool ComplexKeyValuesEqual(EntityKey key1, EntityKey key2)
{
for (int i = 0; i < key1._compositeKeyValues.Length; ++i)
{
if (key1._keyNames[i].Equals(key2._keyNames[i]))
{
if (!Object.Equals(key1._compositeKeyValues[i], key2._compositeKeyValues[i]))
{
return false;
}
}
else
{
// Key names might not be in the same order so try a slower approach that matches
// key names between the keys.
return KeyValuesEqual(key1, key2);
}
}
return true;
}
private static bool KeyValuesEqual(EntityKey key1, EntityKey key2)
{
Dictionary values = new Dictionary();
foreach (EntityKeyMember key1Value in key1.EntityKeyValues)
{
values.Add(key1Value.Key, key1Value.Value);
}
foreach (EntityKeyMember key2Value in key2.EntityKeyValues)
{
object value;
if (!values.TryGetValue(key2Value.Key, out value) || !object.Equals(key2Value.Value, value))
{
return false;
}
values.Remove(key2Value.Key);
}
return true;
}
#endregion
///
/// Returns an array of string/ pairs, one for each key value in this EntityKey,
/// where the string is the key member name and the DbExpression is the value in this EntityKey
/// for that key member, represented as a with the same result
/// type as the key member.
///
/// The command tree to use to create the key value constant expressions
/// The entity set to which this EntityKey refers; used to verify that this key has the required key members
/// The name -> expression mappings for the key member values represented by this EntityKey
internal KeyValuePair[] GetKeyValueExpressions(DbCommandTree tree, EntitySet entitySet)
{
Debug.Assert(!IsTemporary, "GetKeyValueExpressions doesn't make sense for temporary keys - they have no values.");
Debug.Assert(entitySet != null, "GetEntitySet should not return null.");
Debug.Assert(entitySet.Name == _entitySetName, "EntitySet returned from GetEntitySet has incorrect name.");
int numKeyMembers = 0;
if (!IsTemporary)
{
if (_singletonKeyValue != null)
{
numKeyMembers = 1;
}
else
{
numKeyMembers = _compositeKeyValues.Length;
}
}
if (((EntitySetBase)entitySet).ElementType.KeyMembers.Count != numKeyMembers)
{
// If we found an entity set by name that's a different CLR reference
// than the one contained by this EntityKey, the two entity sets could
// be incompatible. The only error case we need to handle here is the
// one where the number of key members differs; other error cases
// will be handled by the command tree builder methods.
//
throw EntityUtil.EntitySetDoesNotMatch("metadataWorkspace", TypeHelpers.GetFullName(entitySet));
}
// Iterate over the internal collection of string->object
// key value pairs and create a list of string->constant
// expression key value pairs.
KeyValuePair[] keyColumns;
if (_singletonKeyValue != null)
{
EdmMember singletonKeyMember = ((EntitySetBase)entitySet).ElementType.KeyMembers[0];
Debug.Assert(singletonKeyMember != null, "Metadata for singleton key member shouldn't be null.");
keyColumns = new KeyValuePair[] {
new KeyValuePair(singletonKeyMember.Name,
tree.CreateConstantExpression(_singletonKeyValue, Helper.GetModelTypeUsage(singletonKeyMember)))
};
}
else
{
keyColumns = new KeyValuePair[_compositeKeyValues.Length];
for (int i = 0; i < _compositeKeyValues.Length; ++i)
{
Debug.Assert(_compositeKeyValues[i] != null, "Values within key-value pairs cannot be null.");
EdmMember keyMember = ((EntitySetBase)entitySet).ElementType.KeyMembers[i];
Debug.Assert(keyMember != null, "Metadata for key members shouldn't be null.");
keyColumns[i] = new KeyValuePair(keyMember.Name,
tree.CreateConstantExpression(_compositeKeyValues[i], Helper.GetModelTypeUsage(keyMember)));
}
}
return keyColumns;
}
///
/// Returns a string representation of this EntityKey, for use in debugging.
/// Note that the returned string contains potentially sensitive information
/// (i.e., key values), and thus shouldn't be publicly exposed.
///
internal string ConcatKeyValue()
{
System.Text.StringBuilder builder = new System.Text.StringBuilder();
builder.Append("EntitySet=").Append(_entitySetName);
if (!IsTemporary)
{
foreach (EntityKeyMember pair in EntityKeyValues)
{
builder.Append(';');
builder.Append(pair.Key).Append("=").Append(pair.Value);
}
}
return builder.ToString();
}
///
/// Returns the appropriate value for the given key name.
///
internal object FindValueByName(string keyName)
{
Debug.Assert(!IsTemporary, "FindValueByName should not be called for temporary keys.");
if (SingletonKeyValue != null)
{
Debug.Assert(_keyNames[0] == keyName, "For a singleton key, the given keyName must match.");
return _singletonKeyValue;
}
else
{
object[] compositeKeyValues = CompositeKeyValues;
for (int i = 0; i < compositeKeyValues.Length; i++)
{
if (keyName == _keyNames[i])
{
return compositeKeyValues[i];
}
}
throw EntityUtil.ArgumentOutOfRange("keyName");
}
}
internal static void GetEntitySetName(string qualifiedEntitySetName, out string entitySet, out string container)
{
entitySet = null;
container = null;
EntityUtil.CheckStringArgument(qualifiedEntitySetName, "qualifiedEntitySetName");
string[] result = qualifiedEntitySetName.Split('.');
if (result.Length != 2)
{
throw EntityUtil.InvalidQualifiedEntitySetName();
}
container = result[0];
entitySet = result[1];
// both parts must be non-empty
if (container == null || container.Length == 0 ||
entitySet == null || entitySet.Length == 0)
{
throw EntityUtil.InvalidQualifiedEntitySetName();
}
ValidateName(container);
ValidateName(entitySet);
}
internal static void ValidateName(string name)
{
if (!System.Data.EntityModel.SchemaObjectModel.Utils.ValidUndottedName(name))
{
throw EntityUtil.EntityKeyInvalidName(name);
}
}
#region Key Value Assignment and Validation
private static void CheckKeyValues(EntitySet entitySet, IEnumerable> entityKeyValues,
out string[] keyNames, out object singletonKeyValue, out object[] compositeKeyValues)
{
Debug.Assert(entitySet != null, "EntitySet should not be null.");
EntityUtil.CheckArgumentNull(entityKeyValues, "entityKeyValues");
int numExpectedKeyValues = 0;
int numActualKeyValues = 0;
ReadOnlyMetadataCollection keyMembers = null;
singletonKeyValue = null;
compositeKeyValues = null;
// Determine if we're a single or composite key.
foreach (KeyValuePair value in entityKeyValues)
{
numActualKeyValues++;
}
keyMembers = ((EntitySetBase)entitySet).ElementType.KeyMembers;
keyNames = ((EntitySetBase)entitySet).ElementType.KeyMemberNames;
numExpectedKeyValues = keyMembers.Count;
Debug.Assert(numExpectedKeyValues > 0, "Metadata returned an invalid number of keys.");
if (numExpectedKeyValues != numActualKeyValues)
{
throw EntityUtil.IncorrectNumberOfKeyValuePairs("entityKeyValues", entitySet.ElementType.FullName, numExpectedKeyValues, numActualKeyValues);
}
if (numExpectedKeyValues == 1)
{
foreach (KeyValuePair keyValuePair in entityKeyValues)
{
if (EntityUtil.IsNull(keyValuePair.Value) || String.IsNullOrEmpty(keyValuePair.Key))
{
throw EntityUtil.NoNullsAllowedInKeyValuePairs("entityKeyValues");
}
if (keyNames[0] != keyValuePair.Key)
{ // Validate key name
Debug.Assert(null != entitySet, "null entityset");
throw EntityUtil.MissingKeyValue("entityKeyValues", keyNames[0], entitySet.ElementType.FullName);
}
singletonKeyValue = keyValuePair.Value;
// Validate type of key value
Type entitySetKeyType = ((PrimitiveType)keyMembers[0].TypeUsage.EdmType).ClrEquivalentType;
if (entitySetKeyType != singletonKeyValue.GetType())
{
throw EntityUtil.IncorrectValueType("entityKeyValues", keyMembers[0].Name, entitySetKeyType.FullName, singletonKeyValue.GetType().FullName);
}
}
}
else
{
compositeKeyValues = new object[numExpectedKeyValues];
int i = 0;
for (i = 0; i < numExpectedKeyValues; ++i)
{
EdmMember keyField = ((EntitySetBase)entitySet).ElementType.KeyMembers[i];
bool foundMember = false;
foreach (KeyValuePair keyValuePair in entityKeyValues)
{
if (keyField.Name == keyValuePair.Key)
{
if (EntityUtil.IsNull(keyValuePair.Value) || String.IsNullOrEmpty(keyValuePair.Key))
{
throw EntityUtil.NoNullsAllowedInKeyValuePairs("entityKeyValues");
}
compositeKeyValues[i] = keyValuePair.Value;
foundMember = true;
// Validate type of key values
Type entitySetKeyType = ((PrimitiveType)keyField.TypeUsage.EdmType).ClrEquivalentType;
if (entitySetKeyType != compositeKeyValues[i].GetType())
{
throw EntityUtil.IncorrectValueType("entityKeyValues", keyField.Name, entitySetKeyType.FullName, compositeKeyValues[i].GetType().FullName);
}
break;
}
}
// Validate Key Name (if we found it or not)
if (!foundMember)
{
throw EntityUtil.MissingKeyValue("entityKeyValues", keyField.Name, entitySet.ElementType.FullName);
}
}
}
}
private static bool CheckKeyValues(IEnumerable> entityKeyValues,
out string[] keyNames, out object singletonKeyValue, out object[] compositeKeyValues)
{
return CheckKeyValues(entityKeyValues, false, false, out keyNames, out singletonKeyValue, out compositeKeyValues);
}
private static bool CheckKeyValues(IEnumerable> entityKeyValues, bool allowNullKeys, bool tokenizeStrings,
out string[] keyNames, out object singletonKeyValue, out object[] compositeKeyValues)
{
EntityUtil.CheckArgumentNull(entityKeyValues, "entityKeyValues");
int numExpectedKeyValues;
int numActualKeyValues = 0;
keyNames = null;
singletonKeyValue = null;
compositeKeyValues = null;
// Determine if we're a single or composite key.
foreach (KeyValuePair value in entityKeyValues)
{
numActualKeyValues++;
}
numExpectedKeyValues = numActualKeyValues;
if (numExpectedKeyValues == 0)
{
if (!allowNullKeys)
{
throw EntityUtil.EntityKeyMustHaveValues("entityKeyValues");
}
}
else
{
keyNames = new string[numExpectedKeyValues];
if (numExpectedKeyValues == 1)
{
lock (_nameLookup)
{
foreach (KeyValuePair keyValuePair in entityKeyValues)
{
if (EntityUtil.IsNull(keyValuePair.Value) || String.IsNullOrEmpty(keyValuePair.Key))
{
throw EntityUtil.NoNullsAllowedInKeyValuePairs("entityKeyValues");
}
ValidateName(keyValuePair.Key);
keyNames[0] = tokenizeStrings ? EntityKey.LookupSingletonName(keyValuePair.Key) : keyValuePair.Key;
singletonKeyValue = keyValuePair.Value;
}
}
}
else
{
compositeKeyValues = new object[numExpectedKeyValues];
int i = 0;
lock (_nameLookup)
{
foreach (KeyValuePair keyValuePair in entityKeyValues)
{
if (EntityUtil.IsNull(keyValuePair.Value) || String.IsNullOrEmpty(keyValuePair.Key))
{
throw EntityUtil.NoNullsAllowedInKeyValuePairs("entityKeyValues");
}
Debug.Assert(null == keyNames[i], "shouldn't have a name yet");
ValidateName(keyValuePair.Key);
keyNames[i] = tokenizeStrings ? EntityKey.LookupSingletonName(keyValuePair.Key) : keyValuePair.Key;
compositeKeyValues[i] = keyValuePair.Value;
i++;
}
}
}
}
return numExpectedKeyValues > 0;
}
///
/// Validates the record parameter passed to the EntityKey constructor,
/// and converts the data into the form required by EntityKey. For singleton keys,
/// this is a single object. For composite keys, this is an object array.
///
/// the entity set metadata object which this key refers to
/// the parameter to validate
/// the number of expected key-value pairs
/// the name of the argument to use in exception messages
/// the validated value(s) (for a composite key, an object array is returned)
private static void CheckKeyValues(EntitySet entitySet, IExtendedDataRecord record,
out string[] keyNames, out object singletonKeyValue, out object[] compositeKeyValues)
{
singletonKeyValue = null;
compositeKeyValues = null;
int numExpectedKeyValues = ((EntitySetBase)entitySet).ElementType.KeyMembers.Count;
keyNames = ((EntitySetBase)entitySet).ElementType.KeyMemberNames;
EntityType entityType = record.DataRecordInfo.RecordType.EdmType as EntityType;
if (entityType == null)
{
throw EntityUtil.DataRecordMustBeEntity();
}
// ensure the type contained by this entity set matches the type contained by the data record
if (entitySet != null && !entitySet.ElementType.IsAssignableFrom(entityType))
{
throw EntityUtil.EntityTypesDoNotMatch(entityType.Name, entitySet.ElementType.FullName);
}
Debug.Assert(numExpectedKeyValues > 0, "Should be expecting a positive number of key-values.");
if (numExpectedKeyValues == 1)
{
// Optimize for a singleton key.
EdmMember member = entityType.KeyMembers[0];
try
{
singletonKeyValue = CheckValue("record", member.Name, record[member.Name], (PrimitiveType)member.TypeUsage.EdmType);
}
catch (IndexOutOfRangeException ex)
{
throw EntityUtil.MissingKeyValue("record", member.Name, entityType.FullName, ex);
}
}
else
{
compositeKeyValues = new object[numExpectedKeyValues];
// grab each key-field from the data record
for (int i = 0; i < numExpectedKeyValues; ++i)
{
EdmMember member = entityType.KeyMembers[i];
try
{
compositeKeyValues[i] = CheckValue("record", member.Name, record[member.Name],
(PrimitiveType)member.TypeUsage.EdmType);
}
catch (IndexOutOfRangeException ex)
{
throw EntityUtil.MissingKeyValue("record", member.Name, entityType.FullName, ex);
}
}
}
}
///
/// Checks whether the given value is valid for the given key field.
///
/// the name of the argument that contains a value of the given CLR type
/// the name of the key field that contains the value of the given CLR type
/// the value to validate
/// the expected type of the value
/// the validated value
/// the value is null
/// the value is of an incorrect type
private static object CheckValue(string argumentName, string keyFieldName, object value, PrimitiveType expectedType)
{
Debug.Assert(expectedType != null, "expectedType cannot be null");
if (EntityUtil.IsNull(value))
{
throw EntityUtil.NoNullsAllowedInKeyValuePairs(argumentName);
}
if (expectedType.ClrEquivalentType != value.GetType())
{
throw EntityUtil.IncorrectValueType(argumentName, keyFieldName, expectedType.ClrEquivalentType.FullName, value.GetType().FullName);
}
return value;
}
///
/// Verify that the types of the objects passed in to be used as keys actually match the types from the model.
/// This error is also caught when the entity is materialized and when the key value is set, at which time it
/// also throws ThrowSetInvalidValue().
/// SQLBUDT 513838. This error is possible and should be caught at run time, not in an assertion.
///
/// The EntitySet to validate against
internal void ValidateEntityKey(EntitySet entitySet)
{
ValidateEntityKey(entitySet, false, null);
}
///
/// Verify that the types of the objects passed in to be used as keys actually match the types from the model.
/// This error is also caught when the entity is materialized and when the key value is set, at which time it
/// also throws ThrowSetInvalidValue().
/// SQLBUDT 513838. This error is possible and should be caught at run time, not in an assertion.
///
/// The EntitySet to validate against
/// Type of the exception to throw
internal void ValidateEntityKey(EntitySet entitySet, bool isArgumentException, string argumentName)
{
if (entitySet != null)
{
ReadOnlyMetadataCollection keyMembers = ((EntitySetBase)entitySet).ElementType.KeyMembers;
if (_singletonKeyValue != null)
{
// 1. Validate number of keys
if (keyMembers.Count != 1)
{
if (isArgumentException)
{
throw EntityUtil.IncorrectNumberOfKeyValuePairs(argumentName, entitySet.ElementType.FullName, keyMembers.Count, 1);
}
else
{
throw EntityUtil.IncorrectNumberOfKeyValuePairsInvalidOperation(entitySet.ElementType.FullName, keyMembers.Count, 1);
}
}
// 2. Validate type of key values
Type entitySetKeyType = ((PrimitiveType)keyMembers[0].TypeUsage.EdmType).ClrEquivalentType;
if (entitySetKeyType != _singletonKeyValue.GetType())
{
if (isArgumentException)
{
throw EntityUtil.IncorrectValueType(argumentName, keyMembers[0].Name, entitySetKeyType.FullName, _singletonKeyValue.GetType().FullName);
}
else
{
throw EntityUtil.IncorrectValueTypeInvalidOperation(keyMembers[0].Name, entitySetKeyType.FullName, _singletonKeyValue.GetType().FullName);
}
}
// 3. Validate key names
if (_keyNames[0] != keyMembers[0].Name)
{
if (isArgumentException)
{
throw EntityUtil.MissingKeyValue(argumentName, keyMembers[0].Name, entitySet.ElementType.FullName);
}
else
{
throw EntityUtil.MissingKeyValueInvalidOperation(keyMembers[0].Name, entitySet.ElementType.FullName);
}
}
}
else if (null != _compositeKeyValues)
{
// 1. Validate number of keys
if (keyMembers.Count != _compositeKeyValues.Length)
{
if (isArgumentException)
{
throw EntityUtil.IncorrectNumberOfKeyValuePairs(argumentName, entitySet.ElementType.FullName, keyMembers.Count, _compositeKeyValues.Length);
}
else
{
throw EntityUtil.IncorrectNumberOfKeyValuePairsInvalidOperation(entitySet.ElementType.FullName, keyMembers.Count, _compositeKeyValues.Length);
}
}
for (int i = 0; i < _compositeKeyValues.Length; ++i)
{
EdmMember keyField = ((EntitySetBase)entitySet).ElementType.KeyMembers[i];
bool foundMember = false;
for(int j = 0; j < _compositeKeyValues.Length; ++j)
{
if (keyField.Name == _keyNames[j])
{
// 2. Validate type of key values
Type entitySetKeyType = ((PrimitiveType)keyField.TypeUsage.EdmType).ClrEquivalentType;
if (entitySetKeyType != _compositeKeyValues[j].GetType())
{
if (isArgumentException)
{
throw EntityUtil.IncorrectValueType(argumentName, keyField.Name, entitySetKeyType.FullName, _compositeKeyValues[j].GetType().FullName);
}
else
{
throw EntityUtil.IncorrectValueTypeInvalidOperation(keyField.Name, entitySetKeyType.FullName, _compositeKeyValues[j].GetType().FullName);
}
}
foundMember = true;
break;
}
}
// 3. Validate Key Name (if we found it or not)
if (!foundMember)
{
if (isArgumentException)
{
throw EntityUtil.MissingKeyValue(argumentName, keyField.Name, entitySet.ElementType.FullName);
}
else
{
throw EntityUtil.MissingKeyValueInvalidOperation(keyField.Name, entitySet.ElementType.FullName);
}
}
}
}
}
}
///
/// Asserts that the "state" of the EntityKey is correct, by validating assumptions
/// based on whether the key is a singleton, composite, or temporary.
///
/// whether we expect this EntityKey to be marked temporary
[Conditional("DEBUG")]
private void AssertCorrectState(EntitySetBase entitySet, bool isTemporary)
{
if (_singletonKeyValue != null)
{
Debug.Assert(!isTemporary, "Singleton keys should not be expected to be temporary.");
Debug.Assert(_compositeKeyValues == null, "The EntityKey is marked as both a singleton key and a composite key - this is illegal.");
if (entitySet != null)
{
Debug.Assert(entitySet.ElementType.KeyMembers.Count == 1, "For a singleton key, the number of key fields must be exactly 1.");
}
}
else if (_compositeKeyValues != null)
{
Debug.Assert(!isTemporary, "Composite keys should not be expected to be temporary.");
if (entitySet != null)
{
Debug.Assert(entitySet.ElementType.KeyMembers.Count > 1, "For a compsite key, the number of key fields should be greater than 1.");
Debug.Assert(entitySet.ElementType.KeyMembers.Count == _compositeKeyValues.Length, "Incorrect number of values specified to composite key.");
}
for (int i = 0; i < _compositeKeyValues.Length; ++i)
{
Debug.Assert(_compositeKeyValues[i] != null, "Values passed to a composite EntityKey cannot be null.");
}
}
else if (!IsTemporary)
{
// one of our static keys
Debug.Assert(!isTemporary, "Static keys should not be expected to be temporary.");
Debug.Assert(this.EntityKeyValues == null, "The EntityKeyValues property for Static EntityKeys must return null.");
Debug.Assert(this.EntityContainerName == null, "The EntityContainerName property for Static EntityKeys must return null.");
Debug.Assert(this.EntitySetName != null, "The EntitySetName property for Static EntityKeys must not return null.");
}
else
{
Debug.Assert(isTemporary, "The EntityKey is marked as neither a singleton or composite. Therefore, it should be expected to be temporary.");
Debug.Assert(this.IsTemporary, "The EntityKey is marked as neither a singleton or composite. Therefore it must be marked as temporary.");
Debug.Assert(this.EntityKeyValues == null, "The EntityKeyValues property for temporary EntityKeys must return null.");
}
}
#endregion
#region Serialization
///
///
///
///
[OnDeserializing]
public void OnDeserializing(StreamingContext context)
{
if (RequiresDeserialization)
{
DeserializeMembers();
}
}
///
///
///
///
[OnDeserialized]
public void OnDeserialized(StreamingContext context)
{
lock (_nameLookup)
{
_entitySetName = LookupSingletonName(_entitySetName);
_entityContainerName = LookupSingletonName(_entityContainerName);
if (_keyNames != null)
{
for (int i = 0; i < _keyNames.Length; i++)
{
_keyNames[i] = LookupSingletonName(_keyNames[i]);
}
}
}
}
///
/// Dev Note: this must be called from within a _lock block on _nameLookup
///
///
///
private static string LookupSingletonName(string name)
{
if (String.IsNullOrEmpty(name))
{
return null;
}
if (_nameLookup.ContainsKey(name))
{
return _nameLookup[name];
}
_nameLookup.Add(name, name);
return name;
}
private void ValidateWritable(object instance)
{
if (_isLocked || instance != null)
{
throw EntityUtil.CannotChangeEntityKey();
}
}
private bool RequiresDeserialization
{
get { return _deserializedMembers != null; }
}
private void DeserializeMembers()
{
if (CheckKeyValues(new KeyValueReader(_deserializedMembers), true, true, out _keyNames, out _singletonKeyValue, out _compositeKeyValues))
{
// If we received values from the _deserializedMembers, then we do not need to track these any more
_deserializedMembers = null;
}
}
#endregion
private class KeyValueReader : IEnumerable>
{
IEnumerable _enumerator;
public KeyValueReader(IEnumerable enumerator)
{
_enumerator = enumerator;
}
#region IEnumerable> Members
public IEnumerator> GetEnumerator()
{
foreach (EntityKeyMember pair in _enumerator)
{
if (pair != null)
{
yield return new KeyValuePair(pair.Key, pair.Value);
}
}
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
#endregion
}
}
///
/// Information about a key that is part of an EntityKey.
/// A key member contains the key name and value.
///
[DataContract]
[Serializable]
public class EntityKeyMember
{
private string _keyName;
private object _keyValue;
///
/// Creates an empty EntityKeyMember. This constructor is used by serialization.
///
public EntityKeyMember()
{
}
///
/// Creates a new EntityKeyMember with the specified key name and value.
///
/// The key name
/// The key value
public EntityKeyMember(string keyName, object keyValue)
{
EntityUtil.CheckArgumentNull(keyName, "keyName");
EntityUtil.CheckArgumentNull(keyValue, "keyValue");
_keyName = keyName;
_keyValue = keyValue;
}
///
/// The key name
///
[DataMember]
public string Key
{
get
{
return _keyName;
}
set
{
ValidateWritable(_keyName);
EntityUtil.CheckArgumentNull(value, "value");
_keyName = value;
}
}
///
/// The key value
///
[DataMember]
public object Value
{
get
{
return _keyValue;
}
set
{
ValidateWritable(_keyValue);
EntityUtil.CheckArgumentNull(value, "value");
_keyValue = value;
}
}
///
/// Returns a string representation of the EntityKeyMember
///
/// A string representation of the EntityKeyMember
public override string ToString()
{
return String.Format(System.Globalization.CultureInfo.CurrentCulture, "[{0}, {1}]", _keyName, _keyValue);
}
///
/// Ensures that the instance can be written to (value must be null)
///
private void ValidateWritable(object instance)
{
if (instance != null)
{
throw EntityUtil.CannotChangeEntityKey();
}
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- XsdDateTime.cs
- ScriptIgnoreAttribute.cs
- RewritingPass.cs
- Dictionary.cs
- WindowsStatusBar.cs
- Scene3D.cs
- DBConnectionString.cs
- LayoutUtils.cs
- TCPClient.cs
- ParameterCollection.cs
- AccessDataSource.cs
- URI.cs
- DefaultParameterValueAttribute.cs
- DataContractJsonSerializer.cs
- XmlStreamNodeWriter.cs
- ListInitExpression.cs
- SQLInt32.cs
- SubclassTypeValidator.cs
- DataContract.cs
- JsonServiceDocumentSerializer.cs
- SingleStorage.cs
- XmlSchemaAttribute.cs
- ResourcePool.cs
- XPathDescendantIterator.cs
- Exceptions.cs
- QilFactory.cs
- Accessible.cs
- DataGridViewCellParsingEventArgs.cs
- TimeoutValidationAttribute.cs
- AssociationTypeEmitter.cs
- FormCollection.cs
- DbDataReader.cs
- CollectionsUtil.cs
- RelatedPropertyManager.cs
- DebugView.cs
- PenThreadPool.cs
- ColumnResult.cs
- BindingsCollection.cs
- DbExpressionRules.cs
- COAUTHINFO.cs
- BuildManagerHost.cs
- HScrollProperties.cs
- IntSecurity.cs
- SafeBitVector32.cs
- MessageCredentialType.cs
- PeoplePickerWrapper.cs
- LZCodec.cs
- DataControlFieldHeaderCell.cs
- PKCS1MaskGenerationMethod.cs
- LocalizableAttribute.cs
- DataControlLinkButton.cs
- TdsParserStateObject.cs
- SmtpClient.cs
- PropertyDescriptors.cs
- XPathSingletonIterator.cs
- HttpWebRequest.cs
- EncoderNLS.cs
- DataSourceIDConverter.cs
- SQLByteStorage.cs
- LookupBindingPropertiesAttribute.cs
- RadioButton.cs
- ExpressionsCollectionEditor.cs
- GenerateScriptTypeAttribute.cs
- SectionInput.cs
- XmlSchemaProviderAttribute.cs
- WebPartTracker.cs
- AnimationClock.cs
- InstanceDataCollection.cs
- PrintingPermissionAttribute.cs
- HeaderPanel.cs
- _WinHttpWebProxyDataBuilder.cs
- FixedFlowMap.cs
- HtmlControlPersistable.cs
- UnsafeNativeMethods.cs
- ProfileSettings.cs
- SqlUtil.cs
- CopyNodeSetAction.cs
- Configuration.cs
- HtmlMeta.cs
- BufferModeSettings.cs
- MetadataWorkspace.cs
- PackageFilter.cs
- XPathDescendantIterator.cs
- SmiXetterAccessMap.cs
- LineMetrics.cs
- SerializationHelper.cs
- LinearGradientBrush.cs
- Model3D.cs
- SqlDependencyListener.cs
- ValueTypeFixupInfo.cs
- LoginUtil.cs
- ListBox.cs
- DataGridColumnFloatingHeader.cs
- ListView.cs
- GeneralTransform.cs
- ArrayHelper.cs
- TextOutput.cs
- AppearanceEditorPart.cs
- WindowsContainer.cs
- UndirectedGraph.cs