536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
1405 lines
59 KiB
C#
1405 lines
59 KiB
C#
//---------------------------------------------------------------------
|
|
// <copyright file="EntityKey.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//
|
|
// @owner Microsoft
|
|
// @backupOwner Microsoft
|
|
//---------------------------------------------------------------------
|
|
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Data.Common;
|
|
using System.Data.Common.CommandTrees;
|
|
using System.Data.Common.CommandTrees.ExpressionBuilder;
|
|
using System.Data.Common.Utils;
|
|
using System.Data.Metadata.Edm;
|
|
using System.Diagnostics;
|
|
using System.Runtime.Serialization;
|
|
using Edm = System.Data.Metadata.Edm;
|
|
|
|
namespace System.Data
|
|
{
|
|
/// <summary>
|
|
/// An identifier for an entity.
|
|
/// </summary>
|
|
[DebuggerDisplay("{ConcatKeyValue()}")]
|
|
[Serializable]
|
|
[DataContract(IsReference = true)]
|
|
public sealed class EntityKey : IEquatable<EntityKey>
|
|
{
|
|
// 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
|
|
|
|
// Determines whether the key includes a byte[].
|
|
// Not serialized for backwards compatibility.
|
|
// This value is computed along with the _hashCode, which is also not serialized.
|
|
[NonSerialized]
|
|
private bool _containsByteArray;
|
|
|
|
[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";
|
|
|
|
/// <summary>
|
|
/// A singleton EntityKey by which a read-only entity is identified.
|
|
/// </summary>
|
|
[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);
|
|
|
|
/// <summary>
|
|
/// A singleton EntityKey identifying an entity resulted from a failed TREAT.
|
|
/// </summary>
|
|
[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);
|
|
|
|
/// <summary>
|
|
/// A dictionary of names so that singleton instances of names can be used
|
|
/// </summary>
|
|
private static Dictionary<string, string> _nameLookup = new Dictionary<string, string>();
|
|
|
|
#region Public Constructors
|
|
|
|
/// <summary>
|
|
/// Constructs an empty EntityKey. For use during XmlSerialization.
|
|
/// </summary>
|
|
public EntityKey()
|
|
{
|
|
_isLocked = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs an EntityKey with the given key values.
|
|
/// </summary>
|
|
/// <param name="qualifiedEntitySetName">The EntitySet name, qualified by the EntityContainer name, of the entity</param>
|
|
/// <param name="entityKeyValues">The key-value pairs that identify the entity</param>
|
|
public EntityKey(string qualifiedEntitySetName, IEnumerable<KeyValuePair<string, object>> entityKeyValues)
|
|
{
|
|
GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
|
|
CheckKeyValues(entityKeyValues, out _keyNames, out _singletonKeyValue, out _compositeKeyValues);
|
|
AssertCorrectState(null, false);
|
|
_isLocked = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs an EntityKey with the given key values.
|
|
/// </summary>
|
|
/// <param name="qualifiedEntitySetName">The EntitySet name, qualified by the EntityContainer name, of the entity</param>
|
|
/// <param name="entityKeyValues">The key-value pairs that identify the entity</param>
|
|
public EntityKey(string qualifiedEntitySetName, IEnumerable<EntityKeyMember> entityKeyValues)
|
|
{
|
|
GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
|
|
EntityUtil.CheckArgumentNull(entityKeyValues, "entityKeyValues");
|
|
CheckKeyValues(new KeyValueReader(entityKeyValues), out _keyNames, out _singletonKeyValue, out _compositeKeyValues);
|
|
AssertCorrectState(null, false);
|
|
_isLocked = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs an EntityKey with the given single key name and value.
|
|
/// </summary>
|
|
/// <param name="qualifiedEntitySetName">The EntitySet name, qualified by the EntityContainer name, of the entity</param>
|
|
/// <param name="keyName">The key name that identifies the entity</param>
|
|
/// <param name="keyValue">The key value that identifies the entity</param>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Constructs an EntityKey from an IExtendedDataRecord representing the entity.
|
|
/// </summary>
|
|
/// <param name="entitySet">EntitySet of the entity</param>
|
|
/// <param name="record">an IExtendedDataRecord that represents the entity</param>
|
|
internal EntityKey(EntitySet entitySet, IExtendedDataRecord record)
|
|
{
|
|
Debug.Assert(entitySet != null, "entitySet is null");
|
|
Debug.Assert(entitySet.Name != null, "entitySet.Name is null");
|
|
Debug.Assert(entitySet.EntityContainer != null, "entitySet.EntityContainer is null");
|
|
Debug.Assert(entitySet.EntityContainer.Name != null, "entitySet.EntityContainer.Name is null");
|
|
Debug.Assert(record != null, "record is null");
|
|
|
|
_entitySetName = entitySet.Name;
|
|
_entityContainerName = entitySet.EntityContainer.Name;
|
|
|
|
GetKeyValues(entitySet, record, out _keyNames, out _singletonKeyValue, out _compositeKeyValues);
|
|
AssertCorrectState(entitySet, false);
|
|
_isLocked = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs an EntityKey from an IExtendedDataRecord representing the entity.
|
|
/// </summary>
|
|
/// <param name="entitySet">EntitySet of the entity</param>
|
|
/// <param name="record">an IExtendedDataRecord that represents the entity</param>
|
|
internal EntityKey(string qualifiedEntitySetName)
|
|
{
|
|
GetEntitySetName(qualifiedEntitySetName, out _entitySetName, out _entityContainerName);
|
|
_isLocked = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a temporary EntityKey with the given EntitySet.
|
|
/// Temporary keys do not store key field names
|
|
/// </summary>
|
|
/// <param name="entitySet">EntitySet of the entity</param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="entitySet">EntitySet of the entity</param>
|
|
/// <param name="singletonKeyValue">The single value that composes the entity's key, assumed to contain the correct type.</param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="entitySet">EntitySet of the entity</param>
|
|
/// <param name="compositeKeyValues">A list of the values (at least 2) that compose the entity's key, assumed to contain correct types.</param>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Gets the EntitySet name identifying the entity set that contains the entity.
|
|
/// </summary>
|
|
[DataMember]
|
|
public string EntitySetName
|
|
{
|
|
get
|
|
{
|
|
return _entitySetName;
|
|
}
|
|
set
|
|
{
|
|
ValidateWritable(_entitySetName);
|
|
lock (_nameLookup)
|
|
{
|
|
_entitySetName = EntityKey.LookupSingletonName(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the EntityContainer name identifying the entity container that contains the entity.
|
|
/// </summary>
|
|
[DataMember]
|
|
public string EntityContainerName
|
|
{
|
|
get
|
|
{
|
|
return _entityContainerName;
|
|
}
|
|
set
|
|
{
|
|
ValidateWritable(_entityContainerName);
|
|
lock (_nameLookup)
|
|
{
|
|
_entityContainerName = EntityKey.LookupSingletonName(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the key values that identify the entity.
|
|
/// </summary>
|
|
[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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether this key is a temporary key.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the entity set for this entity key from the given metadata workspace, by
|
|
/// entity container name and entity set name.
|
|
/// </summary>
|
|
/// <param name="metadataWorkspace">workspace in which to look up the entity set</param>
|
|
/// <returns>the entity set from the given workspace for this entity key</returns>
|
|
/// <exception cref="ArgumentException">the entity set could not be located in the workspace</exception>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Compares this instance to a given key by their values.
|
|
/// </summary>
|
|
/// <param name="obj">the key to compare against this instance</param>
|
|
/// <returns>true if this instance is equal to the given key, and false otherwise</returns>
|
|
public override bool Equals(object obj)
|
|
{
|
|
return InternalEquals(this, obj as EntityKey, compareEntitySets: true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compares this instance to a given key by their values.
|
|
/// </summary>
|
|
/// <param name="other">the key to compare against this instance</param>
|
|
/// <returns>true if this instance is equal to the given key, and false otherwise</returns>
|
|
public bool Equals(EntityKey other)
|
|
{
|
|
return InternalEquals(this, other, compareEntitySets: true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a value-based hash code, to allow EntityKey to be used in hash tables.
|
|
/// </summary>
|
|
/// <returns>the hash value of this EntityKey</returns>
|
|
public override int GetHashCode()
|
|
{
|
|
int hashCode = _hashCode;
|
|
if (0 == hashCode)
|
|
{
|
|
_containsByteArray = false;
|
|
|
|
if (RequiresDeserialization)
|
|
{
|
|
DeserializeMembers();
|
|
}
|
|
|
|
if (_entitySetName != null)
|
|
{
|
|
hashCode = _entitySetName.GetHashCode();
|
|
}
|
|
if (_entityContainerName != null)
|
|
{
|
|
hashCode ^= _entityContainerName.GetHashCode();
|
|
}
|
|
|
|
// If the key is not temporary, determine a hash code based on the value(s) within the key.
|
|
if (null != _singletonKeyValue)
|
|
{
|
|
hashCode = AddHashValue(hashCode, _singletonKeyValue);
|
|
}
|
|
else if (null != _compositeKeyValues)
|
|
{
|
|
for (int i = 0, n = _compositeKeyValues.Length; i < n; i++)
|
|
{
|
|
hashCode = AddHashValue(hashCode, _compositeKeyValues[i]);
|
|
}
|
|
}
|
|
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 int AddHashValue(int hashCode, object keyValue)
|
|
{
|
|
byte[] byteArrayValue = keyValue as byte[];
|
|
if (null != byteArrayValue)
|
|
{
|
|
hashCode ^= ByValueEqualityComparer.ComputeBinaryHashCode(byteArrayValue);
|
|
_containsByteArray = true;
|
|
return hashCode;
|
|
}
|
|
else
|
|
{
|
|
return hashCode ^ keyValue.GetHashCode();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compares two keys by their values.
|
|
/// </summary>
|
|
/// <param name="key1">a key to compare</param>
|
|
/// <param name="key2">a key to compare</param>
|
|
/// <returns>true if the two keys are equal, false otherwise</returns>
|
|
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, compareEntitySets: true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compares two keys by their values.
|
|
/// </summary>
|
|
/// <param name="key1">a key to compare</param>
|
|
/// <param name="key2">a key to compare</param>
|
|
/// <returns>true if the two keys are not equal, false otherwise</returns>
|
|
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, compareEntitySets: true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal function to compare two keys by their values.
|
|
/// </summary>
|
|
/// <param name="key1">a key to compare</param>
|
|
/// <param name="key2">a key to compare</param>
|
|
/// <param name="compareEntitySets">Entity sets are not significant for conceptual null keys</param>
|
|
/// <returns>true if the two keys are equal, false otherwise</returns>
|
|
internal static bool InternalEquals(EntityKey key1, EntityKey key2, bool compareEntitySets)
|
|
{
|
|
// 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 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<EntityKey,ObjectStateEntry>
|
|
// at which point Equals is only called after HashCode was determined to be equal
|
|
if ((key1.GetHashCode() != key2.GetHashCode() && compareEntitySets) ||
|
|
key1._containsByteArray != key2._containsByteArray)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (null != key1._singletonKeyValue)
|
|
{
|
|
if (key1._containsByteArray)
|
|
{
|
|
// Compare the single value (if the second is null, false should be returned)
|
|
if (null == key2._singletonKeyValue)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// they are both byte[] because they have the same _containsByteArray value of true, and only a single value
|
|
if (!ByValueEqualityComparer.CompareBinaryValues((byte[])key1._singletonKeyValue, (byte[])key2._singletonKeyValue))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// not a byte array
|
|
if (!key1._singletonKeyValue.Equals(key2._singletonKeyValue))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check key names
|
|
if (!String.Equals(key1._keyNames[0], key2._keyNames[0]))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 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.
|
|
if (null != key1._compositeKeyValues && null != key2._compositeKeyValues && key1._compositeKeyValues.Length == key2._compositeKeyValues.Length)
|
|
{
|
|
if (key1._containsByteArray)
|
|
{
|
|
if (!CompositeValuesWithBinaryEqual(key1, key2))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!CompositeValuesEqual(key1, key2))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (compareEntitySets)
|
|
{
|
|
// Check metadata.
|
|
if (!String.Equals(key1._entitySetName, key2._entitySetName) ||
|
|
!String.Equals(key1._entityContainerName, key2._entityContainerName))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
internal static bool CompositeValuesWithBinaryEqual(EntityKey key1, EntityKey key2)
|
|
{
|
|
for (int i = 0; i < key1._compositeKeyValues.Length; ++i)
|
|
{
|
|
if (key1._keyNames[i].Equals(key2._keyNames[i]))
|
|
{
|
|
if (!ByValueEqualityComparer.Default.Equals(key1._compositeKeyValues[i], key2._compositeKeyValues[i]))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
// Key names might not be in the same order so try a slower approach that matches
|
|
// key names between the keys.
|
|
else if (!ValuesWithBinaryEqual(key1._keyNames[i], key1._compositeKeyValues[i], key2))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static bool ValuesWithBinaryEqual(string keyName, object keyValue, EntityKey key2)
|
|
{
|
|
for (int i = 0; i < key2._keyNames.Length; i++)
|
|
{
|
|
if (String.Equals(keyName, key2._keyNames[i]))
|
|
{
|
|
return ByValueEqualityComparer.Default.Equals(keyValue, key2._compositeKeyValues[i]);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static bool CompositeValuesEqual(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;
|
|
}
|
|
}
|
|
// Key names might not be in the same order so try a slower approach that matches
|
|
// key names between the keys.
|
|
else if (!ValuesEqual(key1._keyNames[i], key1._compositeKeyValues[i], key2))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static bool ValuesEqual(string keyName, object keyValue, EntityKey key2)
|
|
{
|
|
for (int i = 0; i < key2._keyNames.Length; i++)
|
|
{
|
|
if (String.Equals(keyName, key2._keyNames[i]))
|
|
{
|
|
return Object.Equals(keyValue, key2._compositeKeyValues[i]);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
/// <summary>
|
|
/// Returns an array of string/<see cref="DbExpression"/> 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 <see cref="DbConstantExpression"/> with the same result
|
|
/// type as the key member.
|
|
/// </summary>
|
|
/// <param name="entitySet">The entity set to which this EntityKey refers; used to verify that this key has the required key members</param>
|
|
/// <returns>The name -> expression mappings for the key member values represented by this EntityKey</returns>
|
|
internal KeyValuePair<string, DbExpression>[] GetKeyValueExpressions(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<string, DbExpression>[] 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[] { DbExpressionBuilder.Constant(Helper.GetModelTypeUsage(singletonKeyMember), _singletonKeyValue)
|
|
.As(singletonKeyMember.Name) };
|
|
|
|
}
|
|
else
|
|
{
|
|
keyColumns = new KeyValuePair<string, DbExpression>[_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] = DbExpressionBuilder.Constant(Helper.GetModelTypeUsage(keyMember), _compositeKeyValues[i]).As(keyMember.Name);
|
|
}
|
|
}
|
|
|
|
return keyColumns;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the appropriate value for the given key name.
|
|
/// </summary>
|
|
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 bool CheckKeyValues(IEnumerable<KeyValuePair<string, object>> 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<KeyValuePair<string, object>> 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<string, object> 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<string, object> 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<string, object> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="entitySet">the entity set metadata object which this key refers to</param>
|
|
/// <param name="record">the parameter to validate</param>
|
|
/// <param name="numExpectedKeyValues">the number of expected key-value pairs</param>
|
|
/// <param name="argumentName">the name of the argument to use in exception messages</param>
|
|
/// <param name="workspace">MetadataWorkspace used to resolve and validate enum keys.</param>
|
|
/// <returns>the validated value(s) (for a composite key, an object array is returned)</returns>
|
|
private static void GetKeyValues(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;
|
|
Debug.Assert(entityType != null, "Data record must be an entity.");
|
|
|
|
// assert the type contained by this entity set matches the type contained by the data record
|
|
Debug.Assert(entitySet != null && entitySet.ElementType.IsAssignableFrom(entityType), "Entity types do not match");
|
|
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];
|
|
singletonKeyValue = record[member.Name];
|
|
if (EntityUtil.IsNull(singletonKeyValue))
|
|
{
|
|
throw EntityUtil.NoNullsAllowedInKeyValuePairs("record");
|
|
}
|
|
}
|
|
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];
|
|
compositeKeyValues[i] = record[member.Name];
|
|
if (EntityUtil.IsNull(compositeKeyValues[i]))
|
|
{
|
|
throw EntityUtil.NoNullsAllowedInKeyValuePairs("record");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="workspace">MetadataWorkspace used to resolve and validate types of enum keys.</param>
|
|
/// <param name="entitySet">The EntitySet to validate against</param>
|
|
internal void ValidateEntityKey(MetadataWorkspace workspace, EntitySet entitySet)
|
|
{
|
|
ValidateEntityKey(workspace, entitySet, false, null);
|
|
}
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="workspace">MetadataWorkspace used to resolve and validate types of enum keys.</param>
|
|
/// <param name="entitySet">The EntitySet to validate against</param>
|
|
/// <param name="isArgumentException">Wether to throw ArgumentException or InvalidOperationException.</param>
|
|
/// <param name="argumentName">Name of the argument in case of ArgumentException.</param>
|
|
internal void ValidateEntityKey(MetadataWorkspace workspace, EntitySet entitySet, bool isArgumentException, string argumentName)
|
|
{
|
|
if (entitySet != null)
|
|
{
|
|
ReadOnlyMetadataCollection<EdmMember> 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
|
|
ValidateTypeOfKeyValue(workspace, keyMembers[0], _singletonKeyValue, isArgumentException, argumentName);
|
|
|
|
// 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
|
|
ValidateTypeOfKeyValue(workspace, keyField, _compositeKeyValues[j], isArgumentException, argumentName);
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates whether type of the key matches the type of the key value.
|
|
/// </summary>
|
|
/// <param name="workspace">MetadataWorkspace used to resolve and validate types of enum keys.</param>
|
|
/// <param name="keyMember">Edm key member.</param>
|
|
/// <param name="keyValue">The value of the key.</param>
|
|
/// <param name="isArgumentException">Whether to throw ArgumentException or InvalidOperation exception if validation fails.</param>
|
|
/// <param name="argumentName">Name of the argument to be used for ArgumentExceptions.</param>
|
|
private static void ValidateTypeOfKeyValue(MetadataWorkspace workspace, EdmMember keyMember, object keyValue, bool isArgumentException, string argumentName)
|
|
{
|
|
Debug.Assert(workspace != null, "workspace != null");
|
|
Debug.Assert(keyMember != null, "keyMember != null");
|
|
Debug.Assert(keyValue != null, "keyValue != null");
|
|
Debug.Assert(Helper.IsScalarType(keyMember.TypeUsage.EdmType), "key member must be of a scalar type");
|
|
|
|
EdmType keyMemberEdmType = keyMember.TypeUsage.EdmType;
|
|
|
|
if (Helper.IsPrimitiveType(keyMemberEdmType))
|
|
{
|
|
Type entitySetKeyType = ((PrimitiveType)keyMemberEdmType).ClrEquivalentType;
|
|
if (entitySetKeyType != keyValue.GetType())
|
|
{
|
|
if (isArgumentException)
|
|
{
|
|
throw EntityUtil.IncorrectValueType(argumentName, keyMember.Name, entitySetKeyType.FullName, keyValue.GetType().FullName);
|
|
}
|
|
else
|
|
{
|
|
throw EntityUtil.IncorrectValueTypeInvalidOperation(keyMember.Name, entitySetKeyType.FullName, keyValue.GetType().FullName);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(Helper.IsEnumType(keyMember.TypeUsage.EdmType), "Enum type expected");
|
|
|
|
EnumType expectedEnumType;
|
|
if (workspace.TryGetObjectSpaceType((EnumType)keyMemberEdmType, out expectedEnumType))
|
|
{
|
|
var expectedClrEnumType = ((ClrEnumType)expectedEnumType).ClrType;
|
|
if (expectedClrEnumType != keyValue.GetType())
|
|
{
|
|
if (isArgumentException)
|
|
{
|
|
throw EntityUtil.IncorrectValueType(argumentName, keyMember.Name, expectedClrEnumType.FullName, keyValue.GetType().FullName);
|
|
}
|
|
else
|
|
{
|
|
throw EntityUtil.IncorrectValueTypeInvalidOperation(keyMember.Name, expectedClrEnumType.FullName, keyValue.GetType().FullName);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (isArgumentException)
|
|
{
|
|
throw EntityUtil.NoCorrespondingOSpaceTypeForEnumKeyField(argumentName, keyMember.Name, keyMemberEdmType.FullName);
|
|
}
|
|
else
|
|
{
|
|
throw EntityUtil.NoCorrespondingOSpaceTypeForEnumKeyFieldInvalidOperation(keyMember.Name, keyMemberEdmType.FullName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asserts that the "state" of the EntityKey is correct, by validating assumptions
|
|
/// based on whether the key is a singleton, composite, or temporary.
|
|
/// </summary>
|
|
/// <param name="isTemporary">whether we expect this EntityKey to be marked temporary</param>
|
|
[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 composite 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
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="context"></param>
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
[Browsable(false)]
|
|
[OnDeserializing]
|
|
public void OnDeserializing(StreamingContext context)
|
|
{
|
|
if (RequiresDeserialization)
|
|
{
|
|
DeserializeMembers();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="context"></param>
|
|
[OnDeserialized]
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
[Browsable(false)]
|
|
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]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dev Note: this must be called from within a _lock block on _nameLookup
|
|
/// </summary>
|
|
/// <param name="name"></param>
|
|
/// <returns></returns>
|
|
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<KeyValuePair<string, object>>
|
|
{
|
|
IEnumerable<EntityKeyMember> _enumerator;
|
|
|
|
public KeyValueReader(IEnumerable<EntityKeyMember> enumerator)
|
|
{
|
|
_enumerator = enumerator;
|
|
}
|
|
|
|
#region IEnumerable<KeyValuePair<string,object>> Members
|
|
|
|
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
|
{
|
|
foreach (EntityKeyMember pair in _enumerator)
|
|
{
|
|
if (pair != null)
|
|
{
|
|
yield return new KeyValuePair<string, object>(pair.Key, pair.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IEnumerable Members
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return this.GetEnumerator();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Information about a key that is part of an EntityKey.
|
|
/// A key member contains the key name and value.
|
|
/// </summary>
|
|
[DataContract]
|
|
[Serializable]
|
|
public class EntityKeyMember
|
|
{
|
|
private string _keyName;
|
|
private object _keyValue;
|
|
|
|
/// <summary>
|
|
/// Creates an empty EntityKeyMember. This constructor is used by serialization.
|
|
/// </summary>
|
|
public EntityKeyMember()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new EntityKeyMember with the specified key name and value.
|
|
/// </summary>
|
|
/// <param name="keyName">The key name</param>
|
|
/// <param name="keyValue">The key value</param>
|
|
public EntityKeyMember(string keyName, object keyValue)
|
|
{
|
|
EntityUtil.CheckArgumentNull(keyName, "keyName");
|
|
EntityUtil.CheckArgumentNull(keyValue, "keyValue");
|
|
_keyName = keyName;
|
|
_keyValue = keyValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The key name
|
|
/// </summary>
|
|
[DataMember]
|
|
public string Key
|
|
{
|
|
get
|
|
{
|
|
return _keyName;
|
|
}
|
|
set
|
|
{
|
|
ValidateWritable(_keyName);
|
|
EntityUtil.CheckArgumentNull(value, "value");
|
|
_keyName = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The key value
|
|
/// </summary>
|
|
[DataMember]
|
|
public object Value
|
|
{
|
|
get
|
|
{
|
|
return _keyValue;
|
|
}
|
|
set
|
|
{
|
|
ValidateWritable(_keyValue);
|
|
EntityUtil.CheckArgumentNull(value, "value");
|
|
_keyValue = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a string representation of the EntityKeyMember
|
|
/// </summary>
|
|
/// <returns>A string representation of the EntityKeyMember</returns>
|
|
public override string ToString()
|
|
{
|
|
return String.Format(System.Globalization.CultureInfo.CurrentCulture, "[{0}, {1}]", _keyName, _keyValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensures that the instance can be written to (value must be null)
|
|
/// </summary>
|
|
private void ValidateWritable(object instance)
|
|
{
|
|
if (instance != null)
|
|
{
|
|
throw EntityUtil.CannotChangeEntityKey();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|