|
//---------------------------------------------------------------------
// <copyright file="EntityEntry.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
namespace System.Data.Objects
{
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Common;
using System.Data.Common.Utils;
using System.Data.Metadata.Edm;
using System.Data.Objects.DataClasses;
using System.Data.Objects.Internal;
using System.Diagnostics;
using System.Linq;
internal sealed class EntityEntry : ObjectStateEntry
{
private StateManagerTypeMetadata _cacheTypeMetadata;
private EntityKey _entityKey; // !null if IsKeyEntry or Entity
private IEntityWrapper _wrappedEntity; // Contains null entity if IsKeyEntry
// entity entry change tracking
private BitArray _modifiedFields; // only and always exists if state is Modified or after Delete() on Modified
private List<StateManagerValue> _originalValues; // only exists if _modifiedFields has a true-bit
// The _originalComplexObjects should always contain references to the values of complex objects which are "original"
// at the moment of calling GetComplexObjectSnapshot(). They are used to get original scalar values from _originalValues
// and to check if complex object instance was changed.
private Dictionary<object, Dictionary<int, object>> _originalComplexObjects; // used for POCO Complex Objects change tracking
private bool _requiresComplexChangeTracking;
private bool _requiresScalarChangeTracking;
private bool _requiresAnyChangeTracking;
#region RelationshipEnd fields
/// <summary>
/// Singlely-linked list of RelationshipEntry.
/// One of the ends in the RelationshipEntry must equal this.EntityKey
/// </summary>
private RelationshipEntry _headRelationshipEnds;
/// <summary>
/// Number of RelationshipEntry in the _relationshipEnds list.
/// </summary>
private int _countRelationshipEnds;
#endregion
#region Constructors
// EntityEntry
internal EntityEntry(IEntityWrapper wrappedEntity, EntityKey entityKey, EntitySet entitySet, ObjectStateManager cache,
StateManagerTypeMetadata typeMetadata, EntityState state)
: base(cache, entitySet, state)
{
Debug.Assert(wrappedEntity != null, "entity wrapper cannot be null.");
Debug.Assert(wrappedEntity.Entity != null, "entity cannot be null.");
Debug.Assert(typeMetadata != null, "typeMetadata cannot be null.");
Debug.Assert(entitySet != null, "entitySet cannot be null.");
Debug.Assert((null == (object)entityKey) || (entityKey.EntitySetName == entitySet.Name), "different entitySet");
_wrappedEntity = wrappedEntity;
_cacheTypeMetadata = typeMetadata;
_entityKey = entityKey;
wrappedEntity.ObjectStateEntry = this;
SetChangeTrackingFlags();
}
/// <summary>
/// Looks at the type of entity represented by this entry and sets flags defining the type of
/// change tracking that will be needed. The three main types are:
/// - Pure POCO objects or non-change-tracking proxies which need DetectChanges for everything.
/// - Entities derived from EntityObject which don't need DetectChanges at all.
/// - Change tracking proxies, which only need DetectChanges for complex properties.
/// </summary>
private void SetChangeTrackingFlags()
{
_requiresScalarChangeTracking = Entity != null && !(Entity is IEntityWithChangeTracker);
_requiresComplexChangeTracking = Entity != null &&
(_requiresScalarChangeTracking ||
(WrappedEntity.IdentityType != Entity.GetType() &&
_cacheTypeMetadata.Members.Any(m => m.IsComplex)));
_requiresAnyChangeTracking = Entity != null &&
(!(Entity is IEntityWithRelationships) ||
_requiresComplexChangeTracking ||
_requiresScalarChangeTracking);
}
// KeyEntry
internal EntityEntry(EntityKey entityKey, EntitySet entitySet, ObjectStateManager cache, StateManagerTypeMetadata typeMetadata)
: base(cache, entitySet, EntityState.Unchanged)
{
Debug.Assert((object)entityKey != null, "entityKey cannot be null.");
Debug.Assert(entitySet != null, "extent cannot be null.");
Debug.Assert(typeMetadata != null, "typeMetadata cannot be null.");
Debug.Assert(entityKey.EntitySetName == entitySet.Name, "different entitySet");
_wrappedEntity = EntityWrapperFactory.NullWrapper;
_entityKey = entityKey;
_cacheTypeMetadata = typeMetadata;
SetChangeTrackingFlags();
}
#endregion
#region Public members
override public bool IsRelationship
{
get
{
ValidateState();
return false;
}
}
override public object Entity
{
get
{
ValidateState();
return _wrappedEntity.Entity;
}
}
/// <summary>
/// The EntityKey associated with the ObjectStateEntry
/// </summary>
override public EntityKey EntityKey
{
get
{
ValidateState();
return _entityKey;
}
internal set
{
_entityKey = value;
}
}
internal IEnumerable<Tuple<AssociationSet, ReferentialConstraint>> ForeignKeyDependents
{
get
{
foreach (var foreignKey in ((EntitySet)EntitySet).ForeignKeyDependents)
{
AssociationSet associationSet = foreignKey.Item1;
ReferentialConstraint constraint = foreignKey.Item2;
EntityType dependentType = MetadataHelper.GetEntityTypeForEnd((AssociationEndMember)constraint.ToRole);
if (dependentType.IsAssignableFrom(_cacheTypeMetadata.DataRecordInfo.RecordType.EdmType))
{
yield return foreignKey;
}
}
}
}
internal IEnumerable<Tuple<AssociationSet, ReferentialConstraint>> ForeignKeyPrincipals
{
get
{
foreach (var foreignKey in ((EntitySet)EntitySet).ForeignKeyPrincipals)
{
AssociationSet associationSet = foreignKey.Item1;
ReferentialConstraint constraint = foreignKey.Item2;
EntityType dependentType = MetadataHelper.GetEntityTypeForEnd((AssociationEndMember)constraint.FromRole);
if (dependentType.IsAssignableFrom(_cacheTypeMetadata.DataRecordInfo.RecordType.EdmType))
{
yield return foreignKey;
}
}
}
}
override public IEnumerable<string> GetModifiedProperties()
{
ValidateState();
if (EntityState.Modified == this.State && _modifiedFields != null)
{
Debug.Assert(null != _modifiedFields, "null fields");
for (int i = 0; i < _modifiedFields.Count; i++)
{
if (_modifiedFields[i])
{
yield return (GetCLayerName(i, _cacheTypeMetadata));
}
}
}
}
/// <summary>
/// Marks specified property as modified.
/// </summary>
/// <param name="propertyName">This API recognizes the names in terms of OSpace</param>
/// <exception cref="InvalidOperationException">If State is not Modified or Unchanged</exception>
///
override public void SetModifiedProperty(string propertyName)
{
int ordinal = ValidateAndGetOrdinalForProperty(propertyName, "SetModifiedProperty");
Debug.Assert(State == EntityState.Unchanged || State == EntityState.Modified, "ValidateAndGetOrdinalForProperty should have thrown.");
if (EntityState.Unchanged == State)
{
State = EntityState.Modified;
_cache.ChangeState(this, EntityState.Unchanged, State);
}
SetModifiedPropertyInternal(ordinal);
}
internal void SetModifiedPropertyInternal(int ordinal)
{
if (null == _modifiedFields)
{
_modifiedFields = new BitArray(GetFieldCount(_cacheTypeMetadata));
}
_modifiedFields[ordinal] = true;
}
private int ValidateAndGetOrdinalForProperty(string propertyName, string methodName)
{
EntityUtil.CheckArgumentNull(propertyName, "propertyName");
// Throw for detached entities
ValidateState();
if (IsKeyEntry)
{
throw EntityUtil.CannotModifyKeyEntryState();
}
int ordinal = _cacheTypeMetadata.GetOrdinalforOLayerMemberName(propertyName);
if (ordinal == -1)
{
throw EntityUtil.InvalidModifiedPropertyName(propertyName);
}
if (State == EntityState.Added || State == EntityState.Deleted)
{
// Threw for detached above; this throws for Added or Deleted entities
throw EntityUtil.SetModifiedStates(methodName);
}
return ordinal;
}
/// <summary>
/// Rejects any changes made to the property with the given name since the property was last loaded,
/// attached, saved, or changes were accepted. The orginal value of the property is stored and the
/// property will no longer be marked as modified.
/// </summary>
/// <remarks>
/// If the result is that no properties of the entity are marked as modified, then the entity will
/// be marked as Unchanged.
/// Changes to properties can only rejected for entities that are in the Modified or Unchanged state.
/// Calling this method for entities in other states (Added, Deleted, or Detached) will result in
/// an exception being thrown.
/// Rejecting changes to properties of an Unchanged entity or unchanged properties of a Modifed
/// is a no-op.
/// </remarks>
/// <param name="propertyName">The name of the property to change.</param>
override public void RejectPropertyChanges(string propertyName)
{
int ordinal = ValidateAndGetOrdinalForProperty(propertyName, "RejectPropertyChanges");
if (State == EntityState.Unchanged)
{
// No-op for unchanged entities since all properties must be unchanged.
return;
}
Debug.Assert(State == EntityState.Modified, "Should have handled all other states above.");
if (_modifiedFields != null && _modifiedFields[ordinal])
{
// Reject the change by setting the current value to the original value
DetectChangesInComplexProperties();
var originalValue = GetOriginalEntityValue(_cacheTypeMetadata, ordinal, _wrappedEntity.Entity, ObjectStateValueRecord.OriginalReadonly);
SetCurrentEntityValue(_cacheTypeMetadata, ordinal, _wrappedEntity.Entity, originalValue);
_modifiedFields[ordinal] = false;
// Check if any properties remain modified. If any are modified, then we leave the entity state as Modified and we are done.
for (int i = 0; i < _modifiedFields.Count; i++)
{
if (_modifiedFields[i])
{
return;
}
}
// No properties are modified so change the state of the entity to Unchanged.
ChangeObjectState(EntityState.Unchanged);
}
}
/// <summary>
/// Original values of entity
/// </summary>
/// <param></param>
/// <returns> DbDataRecord </returns>
[DebuggerBrowsable(DebuggerBrowsableState.Never)] // don't have debugger view expand this
override public DbDataRecord OriginalValues
{
get
{
return InternalGetOriginalValues(true /*readOnly*/);
}
}
/// <summary>
/// Gets a version of the OriginalValues property that can be updated
/// </summary>
public override OriginalValueRecord GetUpdatableOriginalValues()
{
return (OriginalValueRecord)InternalGetOriginalValues(false /*readOnly*/);
}
private DbDataRecord InternalGetOriginalValues(bool readOnly)
{
ValidateState();
if (this.State == EntityState.Added)
{
throw EntityUtil.OriginalValuesDoesNotExist();
}
if (this.IsKeyEntry)
{
throw EntityUtil.CannotAccessKeyEntryValues();
}
else
{
DetectChangesInComplexProperties();
if (readOnly)
{
return new ObjectStateEntryDbDataRecord(this, _cacheTypeMetadata, _wrappedEntity.Entity);
}
else
{
return new ObjectStateEntryOriginalDbUpdatableDataRecord_Public(this, _cacheTypeMetadata, _wrappedEntity.Entity, s_EntityRoot);
}
}
}
private void DetectChangesInComplexProperties()
{
if (this.RequiresScalarChangeTracking)
{
// POCO: the snapshot of complex objects has to be updated
// without chaning state of the entry or marking properties as modified.
// The IsOriginalValuesGetter is used in EntityMemberChanged to skip the state transition.
// The snapshot has to be updated in case the complex object instance was changed (not only scalar values).
this.ObjectStateManager.TransactionManager.BeginOriginalValuesGetter();
try
{
// Process only complex objects. The method will not change the state of the entry.
this.DetectChangesInProperties(true /*detectOnlyComplexProperties*/);
}
finally
{
this.ObjectStateManager.TransactionManager.EndOriginalValuesGetter();
}
}
}
/// <summary>
/// Current values of entity/ DataRow
/// </summary>
/// <param></param>
/// <returns> DbUpdatableDataRecord </returns>
[DebuggerBrowsable(DebuggerBrowsableState.Never)] // don't have debugger view expand this
override public CurrentValueRecord CurrentValues
{
get
{
ValidateState();
if (this.State == EntityState.Deleted)
{
throw EntityUtil.CurrentValuesDoesNotExist();
}
if (this.IsKeyEntry)
{
throw EntityUtil.CannotAccessKeyEntryValues();
}
else
{
return new ObjectStateEntryDbUpdatableDataRecord(this, _cacheTypeMetadata, _wrappedEntity.Entity);
}
}
}
override public void Delete()
{
// doFixup flag is used for Cache and Collection & Ref consistency
// When some entity is deleted if "doFixup" is true then Delete method
// calls the Collection & Ref code to do the necessary fix-ups.
// "doFixup" equals to False is only called from EntityCollection & Ref code
Delete(/*doFixup*/true);
}
/// <summary>
/// API to accept the current values as original values and mark the entity as Unchanged.
/// </summary>
/// <param></param>
/// <returns></returns>
override public void AcceptChanges()
{
ValidateState();
if (ObjectStateManager.EntryHasConceptualNull(this))
{
throw new InvalidOperationException(System.Data.Entity.Strings.ObjectContext_CommitWithConceptualNull);
}
Debug.Assert(!this.IsKeyEntry || State == EntityState.Unchanged, "Key ObjectStateEntries must always be unchanged.");
switch (State)
{
case EntityState.Deleted:
this.CascadeAcceptChanges();
// Current entry could be already detached if this is relationship entry and if one end of relationship was a KeyEntry
if (_cache != null)
{
_cache.ChangeState(this, EntityState.Deleted, EntityState.Detached);
}
break;
case EntityState.Added:
// If this entry represents an entity, perform key fixup.
Debug.Assert(Entity != null, "Non-relationship entries should have a non-null entity.");
Debug.Assert((object)_entityKey != null, "All entities in the state manager should have a non-null EntityKey.");
Debug.Assert(_entityKey.IsTemporary, "All entities in the Added state should have a temporary EntityKey.");
// Retrieve referential constraint properties from Principal entities (possibly recursively)
// and check referential constraint properties in the Dependent entities (1 level only)
// We have to do this before fixing up keys to preserve v1 behavior around when stubs are promoted.
// However, we can't check FKs until after fixup, which happens after key fixup. Therefore,
// we keep track of whether or not we need to go check again after fixup. Also, checking for independent associations
// happens using RelationshipEntries, while checking for constraints in FKs has to use the graph.
bool skippedFKs = RetrieveAndCheckReferentialConstraintValuesInAcceptChanges();
_cache.FixupKey(this);
_modifiedFields = null;
_originalValues = null;
_originalComplexObjects = null;
State = EntityState.Unchanged;
if (skippedFKs)
{
// If we skipped checking constraints on any FK relationships above, then
// do it now on the fixuped RelatedEnds.
RelationshipManager.CheckReferentialConstraintProperties(this);
}
_wrappedEntity.TakeSnapshot(this);
break;
case EntityState.Modified:
_cache.ChangeState(this, EntityState.Modified, EntityState.Unchanged);
_modifiedFields = null;
_originalValues = null;
_originalComplexObjects = null;
State = EntityState.Unchanged;
_cache.FixupReferencesByForeignKeys(this);
// Need to check constraints here because fixup could have got us into an invalid state
RelationshipManager.CheckReferentialConstraintProperties(this);
_wrappedEntity.TakeSnapshot(this);
break;
case EntityState.Unchanged:
break;
}
}
override public void SetModified()
{
ValidateState();
if (this.IsKeyEntry)
{
throw EntityUtil.CannotModifyKeyEntryState();
}
else
{
if (EntityState.Unchanged == State)
{
State = EntityState.Modified;
_cache.ChangeState(this, EntityState.Unchanged, State);
}
else if (EntityState.Modified != State)
{
throw EntityUtil.SetModifiedStates("SetModified");
}
}
}
override public RelationshipManager RelationshipManager
{
get
{
ValidateState();
if (IsKeyEntry)
{
throw new InvalidOperationException(System.Data.Entity.Strings.ObjectStateEntry_RelationshipAndKeyEntriesDoNotHaveRelationshipManagers);
}
if (WrappedEntity.Entity == null)
{
throw new InvalidOperationException(System.Data.Entity.Strings.ObjectStateManager_CannotGetRelationshipManagerForDetachedPocoEntity);
}
return WrappedEntity.RelationshipManager;
}
}
internal override BitArray ModifiedProperties
{
get { return _modifiedFields; }
}
/// <summary>
/// Changes state of the entry to the specified <paramref name="state"/>
/// </summary>
/// <param name="state">The requested state</param>
public override void ChangeState(EntityState state)
{
EntityUtil.CheckValidStateForChangeEntityState(state);
if (this.State == EntityState.Detached && state == EntityState.Detached)
{
return;
}
ValidateState();
// store a referece to the cache because this.ObjectStatemanager will be null if the requested state is Detached
ObjectStateManager osm = this.ObjectStateManager;
osm.TransactionManager.BeginLocalPublicAPI();
try
{
this.ChangeObjectState(state);
}
finally
{
osm.TransactionManager.EndLocalPublicAPI();
}
}
/// <summary>
/// Apply modified properties to the original object.
/// </summary>
/// <param name="currentEntity">object with modified properties</param>
public override void ApplyCurrentValues(object currentEntity)
{
EntityUtil.CheckArgumentNull(currentEntity, "currentEntity");
ValidateState();
if (this.IsKeyEntry)
{
throw EntityUtil.CannotAccessKeyEntryValues();
}
IEntityWrapper wrappedEntity = EntityWrapperFactory.WrapEntityUsingStateManager(currentEntity, this.ObjectStateManager);
this.ApplyCurrentValuesInternal(wrappedEntity);
}
/// <summary>
/// Apply original values to the entity.
/// </summary>
/// <param name="originalEntity">The object with original values</param>
public override void ApplyOriginalValues(object originalEntity)
{
EntityUtil.CheckArgumentNull(originalEntity, "originalEntity");
ValidateState();
if (this.IsKeyEntry)
{
throw EntityUtil.CannotAccessKeyEntryValues();
}
IEntityWrapper wrappedEntity = EntityWrapperFactory.WrapEntityUsingStateManager(originalEntity, this.ObjectStateManager);
this.ApplyOriginalValuesInternal(wrappedEntity);
}
#endregion // Public members
#region RelationshipEnd methods
/// <summary>
/// Add a RelationshipEntry (one of its ends must equal this.EntityKey)
/// </summary>
internal void AddRelationshipEnd(RelationshipEntry item)
{
#if DEBUG
Debug.Assert(null != item, "null item");
Debug.Assert(null != item.RelationshipWrapper, "null RelationshipWrapper");
Debug.Assert(0 <= _countRelationshipEnds, "negative _relationshipEndCount");
Debug.Assert(EntityKey.Equals(item.RelationshipWrapper.Key0) || EntityKey.Equals(item.RelationshipWrapper.Key1), "entity key doesn't match");
for (RelationshipEntry current = _headRelationshipEnds;
null != current;
current = current.GetNextRelationshipEnd(EntityKey))
{
Debug.Assert(!Object.ReferenceEquals(item, current), "RelationshipEntry already in list");
Debug.Assert(!item.RelationshipWrapper.Equals(current.RelationshipWrapper), "RelationshipWrapper already in list");
}
#endif
// the item will become the head of the list
// i.e. you walk the list in reverse order of items being added
item.SetNextRelationshipEnd(this.EntityKey, _headRelationshipEnds);
_headRelationshipEnds = item;
_countRelationshipEnds++;
Debug.Assert(_countRelationshipEnds == (new RelationshipEndEnumerable(this)).ToArray().Length, "different count");
}
/// <summary>
/// Determines if a given relationship entry is present in the list of entries
/// </summary>
/// <param name="item">The entry to look for</param>
/// <returns>True of the relationship end is found</returns>
internal bool ContainsRelationshipEnd(RelationshipEntry item)
{
for (RelationshipEntry current = _headRelationshipEnds;
null != current;
current = current.GetNextRelationshipEnd(EntityKey))
{
if (object.ReferenceEquals(current, item))
{
return true;
}
}
return false;
}
/// <summary>
/// Remove a RelationshipEntry (one of its ends must equal this.EntityKey)
/// </summary>
/// <param name="item"></param>
internal void RemoveRelationshipEnd(RelationshipEntry item)
{
Debug.Assert(null != item, "removing null");
Debug.Assert(null != item.RelationshipWrapper, "null RelationshipWrapper");
Debug.Assert(1 <= _countRelationshipEnds, "negative _relationshipEndCount");
Debug.Assert(EntityKey.Equals(item.RelationshipWrapper.Key0) || EntityKey.Equals(item.RelationshipWrapper.Key1), "entity key doesn't match");
// walk the singly-linked list, remembering the previous node so we can remove the current node
RelationshipEntry current = _headRelationshipEnds;
RelationshipEntry previous = null;
bool previousIsKey0 = false;
while (null != current)
{
// short-circuit if the key matches either candidate by reference
bool currentIsKey0 = object.ReferenceEquals(this.EntityKey, current.Key0) ||
(!object.ReferenceEquals(this.EntityKey, current.Key1) && this.EntityKey.Equals(current.Key0));
if (Object.ReferenceEquals(item, current))
{
RelationshipEntry next;
if (currentIsKey0)
{ // if this.EntityKey matches Key0, NextKey0 is the next element in the lsit
Debug.Assert(EntityKey.Equals(current.RelationshipWrapper.Key0), "entity key didn't match");
next = current.NextKey0;
current.NextKey0 = null;
}
else
{ // if this.EntityKey matches Key1, NextKey1 is the next element in the lsit
Debug.Assert(EntityKey.Equals(current.RelationshipWrapper.Key1), "entity key didn't match");
next = current.NextKey1;
current.NextKey1 = null;
}
if (null == previous)
{
_headRelationshipEnds = next;
}
else if (previousIsKey0)
{
previous.NextKey0 = next;
}
else
{
previous.NextKey1 = next;
}
--_countRelationshipEnds;
Debug.Assert(_countRelationshipEnds == (new RelationshipEndEnumerable(this)).ToArray().Length, "different count");
return;
}
Debug.Assert(!item.RelationshipWrapper.Equals(current.RelationshipWrapper), "same wrapper, different RelationshipEntry instances");
previous = current;
current = currentIsKey0 ? current.NextKey0 : current.NextKey1;
previousIsKey0 = currentIsKey0;
}
Debug.Assert(false, "didn't remove a RelationshipEntry");
}
/// <summary>
/// Update one of the ends for the related RelationshipEntry
/// </summary>
/// <param name="oldKey">the EntityKey the relationship should currently have</param>
/// <param name="promotedEntry">if promoting entity stub to full entity</param>
internal void UpdateRelationshipEnds(EntityKey oldKey, EntityEntry promotedEntry)
{
Debug.Assert(null != (object)oldKey, "bad oldKey");
Debug.Assert(!Object.ReferenceEquals(this, promotedEntry), "shouldn't be same reference");
// traverse the list to update one of the ends in the relationship entry
int count = 0;
RelationshipEntry next = _headRelationshipEnds;
while (null != next)
{
// get the next relationship end before we change the key of current relationship end
RelationshipEntry current = next;
next = next.GetNextRelationshipEnd(oldKey);
// update the RelationshipEntry from the temporary key to real key
current.ChangeRelatedEnd(oldKey, EntityKey);
// If we have a promoted entry, copy the relationship entries to the promoted entry
// only if the promoted entry doesn't already know about that particular relationship entry
// This can be the case with self referencing entities
if (null != promotedEntry && !promotedEntry.ContainsRelationshipEnd(current))
{ // all relationship ends moved to new promotedEntry
promotedEntry.AddRelationshipEnd(current);
}
++count;
}
Debug.Assert(count == _countRelationshipEnds, "didn't traverse all relationships");
if (null != promotedEntry)
{ // cleanup existing (dead) entry to reduce confusion
_headRelationshipEnds = null;
_countRelationshipEnds = 0;
}
}
#region Enumerable and Enumerator
internal RelationshipEndEnumerable GetRelationshipEnds()
{
return new RelationshipEndEnumerable(this);
}
/// <summary>
/// An enumerable so that EntityEntry doesn't implement it
/// </summary>
internal struct RelationshipEndEnumerable : IEnumerable<RelationshipEntry>, IEnumerable<IEntityStateEntry>
{
internal static readonly RelationshipEntry[] EmptyRelationshipEntryArray = new RelationshipEntry[0];
private readonly EntityEntry _entityEntry;
internal RelationshipEndEnumerable(EntityEntry entityEntry)
{ // its okay if entityEntry is null
_entityEntry = entityEntry;
}
public RelationshipEndEnumerator GetEnumerator()
{
return new RelationshipEndEnumerator(_entityEntry);
}
IEnumerator<IEntityStateEntry> IEnumerable<IEntityStateEntry>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator<RelationshipEntry> IEnumerable<RelationshipEntry>.GetEnumerator()
{
Debug.Assert(false, "dead code, don't box the RelationshipEndEnumerable");
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
Debug.Assert(false, "dead code, don't box the RelationshipEndEnumerable");
return GetEnumerator();
}
/// <summary>
/// Convert the singly-linked list into an Array
/// </summary>
internal RelationshipEntry[] ToArray()
{
RelationshipEntry[] list = null;
if ((null != _entityEntry) && (0 < _entityEntry._countRelationshipEnds))
{
RelationshipEntry relationshipEnd = _entityEntry._headRelationshipEnds;
list = new RelationshipEntry[_entityEntry._countRelationshipEnds];
for (int i = 0; i < list.Length; ++i)
{
Debug.Assert(null != relationshipEnd, "count larger than list");
Debug.Assert(_entityEntry.EntityKey.Equals(relationshipEnd.Key0) || _entityEntry.EntityKey.Equals(relationshipEnd.Key1), "entity key mismatch");
list[i] = relationshipEnd;
relationshipEnd = relationshipEnd.GetNextRelationshipEnd(_entityEntry.EntityKey);
}
Debug.Assert(null == relationshipEnd, "count smaller than list");
}
return list ?? EmptyRelationshipEntryArray;
}
}
/// <summary>
/// An enumerator to walk the RelationshipEntry linked-list
/// </summary>
internal struct RelationshipEndEnumerator : IEnumerator<RelationshipEntry>, IEnumerator<IEntityStateEntry>
{
private readonly EntityEntry _entityEntry;
private RelationshipEntry _current;
internal RelationshipEndEnumerator(EntityEntry entityEntry)
{
_entityEntry = entityEntry;
_current = null;
}
public RelationshipEntry Current
{
get { return _current; }
}
IEntityStateEntry IEnumerator<IEntityStateEntry>.Current
{
get { return _current; }
}
object IEnumerator.Current
{
get
{
Debug.Assert(false, "dead code, don't box the RelationshipEndEnumerator");
return _current;
}
}
public void Dispose()
{
}
public bool MoveNext()
{
if (null != _entityEntry)
{
if (null == _current)
{
_current = _entityEntry._headRelationshipEnds;
}
else
{
_current = _current.GetNextRelationshipEnd(_entityEntry.EntityKey);
}
}
return (null != _current);
}
public void Reset()
{
Debug.Assert(false, "not implemented");
}
}
#endregion
#endregion
#region ObjectStateEntry members
override internal bool IsKeyEntry
{
get
{
return null == _wrappedEntity.Entity;
}
}
/// <summary>
/// Reuse or create a new (Entity)DataRecordInfo.
/// </summary>
override internal DataRecordInfo GetDataRecordInfo(StateManagerTypeMetadata metadata, object userObject)
{
if (Helper.IsEntityType(metadata.CdmMetadata.EdmType) && (null != (object)_entityKey))
{
// is EntityType with null EntityKey when constructing new EntityKey during ObjectStateManager.Add
// always need a new EntityRecordInfo instance for the different key (reusing DataRecordInfo's FieldMetadata).
return new EntityRecordInfo(metadata.DataRecordInfo, _entityKey, (EntitySet)EntitySet);
}
else
{
// ObjectContext.AttachTo uses CurrentValueRecord to build EntityKey for EntityType
// so the Entity doesn't have an EntityKey yet, SQLBU 525130
//Debug.Assert(Helper.IsComplexType(metadata.CdmMetadata.EdmType), "!IsComplexType");
return metadata.DataRecordInfo;
}
}
override internal void Reset()
{
Debug.Assert(_cache != null, "Cannot Reset an entity that is not currently attached to a context.");
RemoveFromForeignKeyIndex();
_cache.ForgetEntryWithConceptualNull(this, resetAllKeys: true);
DetachObjectStateManagerFromEntity();
_wrappedEntity = EntityWrapperFactory.NullWrapper;
_entityKey = null;
_modifiedFields = null;
_originalValues = null;
_originalComplexObjects = null;
SetChangeTrackingFlags();
base.Reset();
}
override internal Type GetFieldType(int ordinal, StateManagerTypeMetadata metadata)
{
// 'metadata' is used for ComplexTypes
return metadata.GetFieldType(ordinal);
}
override internal string GetCLayerName(int ordinal, StateManagerTypeMetadata metadata)
{
return metadata.CLayerMemberName(ordinal);
}
override internal int GetOrdinalforCLayerName(string name, StateManagerTypeMetadata metadata)
{
return metadata.GetOrdinalforCLayerMemberName(name);
}
override internal void RevertDelete()
{
// just change the state from deleted, to last state.
State = (_modifiedFields == null) ? EntityState.Unchanged : EntityState.Modified;
_cache.ChangeState(this, EntityState.Deleted, State);
}
override internal int GetFieldCount(StateManagerTypeMetadata metadata)
{
return metadata.FieldCount;
}
private void CascadeAcceptChanges()
{
foreach (RelationshipEntry entry in _cache.CopyOfRelationshipsByKey(EntityKey))
{
// CascadeAcceptChanges is only called on Entity ObjectStateEntry when it is
// in deleted state. Entity is in deleted state therefore for all related Relationship
// cache entries only valid state is Deleted.
Debug.Assert(entry.State == EntityState.Deleted, "Relationship ObjectStateEntry should be in deleted state");
entry.AcceptChanges();
}
}
override internal void SetModifiedAll()
{
Debug.Assert(!this.IsKeyEntry, "SetModifiedAll called on a KeyEntry");
Debug.Assert(State == EntityState.Modified, "SetModifiedAll called when not modified");
ValidateState();
if (null == _modifiedFields)
{
_modifiedFields = new BitArray(GetFieldCount(_cacheTypeMetadata));
}
_modifiedFields.SetAll(true);
}
/// <summary>
/// Used to report that a scalar entity property is about to change
/// The current value of the specified property is cached when this method is called.
/// </summary>
/// <param name="entityMemberName">The name of the entity property that is changing</param>
override internal void EntityMemberChanging(string entityMemberName)
{
if (this.IsKeyEntry)
{
throw EntityUtil.CannotAccessKeyEntryValues();
}
this.EntityMemberChanging(entityMemberName, null, null);
}
/// <summary>
/// Used to report that a scalar entity property has been changed
/// The property value that was cached during EntityMemberChanging is now
/// added to OriginalValues
/// </summary>
/// <param name="entityMemberName">The name of the entity property that has changing</param>
override internal void EntityMemberChanged(string entityMemberName)
{
if (this.IsKeyEntry)
{
throw EntityUtil.CannotAccessKeyEntryValues();
}
this.EntityMemberChanged(entityMemberName, null, null);
}
/// <summary>
/// Used to report that a complex property is about to change
/// The current value of the specified property is cached when this method is called.
/// </summary>
/// <param name="entityMemberName">The name of the top-level entity property that is changing</param>
/// <param name="complexObject">The complex object that contains the property that is changing</param>
/// <param name="complexObjectMemberName">The name of the property that is changing on complexObject</param>
override internal void EntityComplexMemberChanging(string entityMemberName, object complexObject, string complexObjectMemberName)
{
if (this.IsKeyEntry)
{
throw EntityUtil.CannotAccessKeyEntryValues();
}
EntityUtil.CheckArgumentNull(complexObjectMemberName, "complexObjectMemberName");
EntityUtil.CheckArgumentNull(complexObject, "complexObject");
this.EntityMemberChanging(entityMemberName, complexObject, complexObjectMemberName);
}
/// <summary>
/// Used to report that a complex property has been changed
/// The property value that was cached during EntityMemberChanging is now added to OriginalValues
/// </summary>
/// <param name="entityMemberName">The name of the top-level entity property that has changed</param>
/// <param name="complexObject">The complex object that contains the property that changed</param>
/// <param name="complexObjectMemberName">The name of the property that changed on complexObject</param>
override internal void EntityComplexMemberChanged(string entityMemberName, object complexObject, string complexObjectMemberName)
{
if (this.IsKeyEntry)
{
throw EntityUtil.CannotAccessKeyEntryValues();
}
EntityUtil.CheckArgumentNull(complexObjectMemberName, "complexObjectMemberName");
EntityUtil.CheckArgumentNull(complexObject, "complexObject");
this.EntityMemberChanged(entityMemberName, complexObject, complexObjectMemberName);
}
#endregion
internal IEntityWrapper WrappedEntity
{
get
{
return _wrappedEntity;
}
}
/// <summary>
/// Method called to complete the change tracking process on an entity property. The original property value
/// is now saved in the original values record if there is not already an entry in the record for this property.
/// The parameters to this method must have the same values as the parameter values passed to the last call to
/// EntityValueChanging on this ObjectStateEntry.
/// All inputs are in OSpace.
/// </summary>
/// <param name="entityMemberName">Name of the top-level entity property that has changed</param>
/// <param name="complexObject">If entityMemberName refers to a complex property, this is the complex
/// object that contains the change. Otherwise this is null.</param>
/// <param name="complexObjectMemberName">If entityMemberName refers to a complex property, this is the name of
/// the property that has changed on complexObject. Otherwise this is null.</param>
private void EntityMemberChanged(string entityMemberName, object complexObject, string complexObjectMemberName)
{
string changingMemberName;
StateManagerTypeMetadata typeMetadata;
object changingObject;
// Get the metadata for the property that is changing, and verify that it is valid to change it for this entry
// If something fails, we will clear out our cached values in the finally block, and require the user to submit another Changing notification
try
{
int changingOrdinal = this.GetAndValidateChangeMemberInfo(entityMemberName, complexObject, complexObjectMemberName,
out typeMetadata, out changingMemberName, out changingObject);
// if EntityKey is changing and is in a valid scenario for it to change, no further action is needed
if (changingOrdinal == -2)
{
return;
}
// Verify that the inputs to this call match the values we have cached
if ((changingObject != _cache.ChangingObject) ||
(changingMemberName != _cache.ChangingMember) ||
(entityMemberName != _cache.ChangingEntityMember))
{
throw EntityUtil.EntityValueChangedWithoutEntityValueChanging();
}
// check the state after the other values because if the other cached values have not been set and are null, it is more
// intuitive to the user to get an error that specifically points to that as the problem, and in that case, the state will
// also not be matched, so if we checked this first, it would cause a confusing error to be thrown.
if (this.State != _cache.ChangingState)
{
throw EntityUtil.ChangedInDifferentStateFromChanging(this.State, _cache.ChangingState);
}
object oldValue = _cache.ChangingOldValue;
object newValue = null;
StateManagerMemberMetadata memberMetadata = null;
if (_cache.SaveOriginalValues)
{
memberMetadata = typeMetadata.Member(changingOrdinal);
// Expand only non-null complex type values
if (memberMetadata.IsComplex && oldValue != null)
{
// devnote: Not using GetCurrentEntityValue here because change tracking can only be done on OSpace members,
// so we don't need to worry about shadow state, and we don't want a CSpace representation of complex objects
newValue = memberMetadata.GetValue(changingObject);
ExpandComplexTypeAndAddValues(memberMetadata, oldValue, newValue, false);
}
else
{
AddOriginalValue(memberMetadata, changingObject, oldValue);
}
}
// if the property is a Foreign Key, let's clear out the appropriate EntityReference
// UNLESS we are applying FK changes as part of DetectChanges where we don't want to
// start changing references yet. If we are in the Align stage of DetectChanges, this is ok.
TransactionManager transManager = ObjectStateManager.TransactionManager;
List<Pair<string, string>> relationships;
if (complexObject == null && // check if property is a top-level property
(transManager.IsAlignChanges || !transManager.IsDetectChanges) &&
this.IsPropertyAForeignKey(entityMemberName, out relationships))
{
foreach (var relationship in relationships)
{
string relationshipName = relationship.First;
string targetRoleName = relationship.Second;
RelatedEnd relatedEnd = this.WrappedEntity.RelationshipManager.GetRelatedEndInternal(relationshipName, targetRoleName);
Debug.Assert(relatedEnd != null, "relatedEnd should exist if property is a foreign key");
EntityReference reference = relatedEnd as EntityReference;
Debug.Assert(reference != null, "relatedEnd should be an EntityReference");
// Allow updating of other relationships that this FK property participates in except that
// if we're doing fixup by references as part of AcceptChanges then don't allow a ref to
// be changed.
if (!transManager.IsFixupByReference)
{
if (memberMetadata == null)
{
memberMetadata = typeMetadata.Member(changingOrdinal);
}
if (newValue == null)
{
newValue = memberMetadata.GetValue(changingObject);
}
bool hasConceptualNullFk = ForeignKeyFactory.IsConceptualNullKey(reference.CachedForeignKey);
if (!ByValueEqualityComparer.Default.Equals(oldValue, newValue) || hasConceptualNullFk)
{
FixupEntityReferenceByForeignKey(reference);
}
}
}
}
// POCO: The state of the entry is not changed if the EntityMemberChanged method
// was called from ObjectStateEntry.OriginalValues property.
// The OriginalValues uses EntityMemberChanging/EntityMemberChanged to update snapshot of complex object in case
// complex object was changed (not a scalar value).
if (_cache != null && !_cache.TransactionManager.IsOriginalValuesGetter)
{
EntityState initialState = State;
if (State != EntityState.Added)
{
State = EntityState.Modified;
}
if (State == EntityState.Modified)
{
SetModifiedProperty(entityMemberName);
}
if (initialState != this.State)
{
_cache.ChangeState(this, initialState, this.State);
}
}
}
finally
{
Debug.Assert(_cache != null, "Unexpected null state manager.");
SetCachedChangingValues(null, null, null, EntityState.Detached, null);
}
}
// helper method used to set value of property
internal void SetCurrentEntityValue(string memberName, object newValue)
{
int ordinal = _cacheTypeMetadata.GetOrdinalforOLayerMemberName(memberName);
SetCurrentEntityValue(_cacheTypeMetadata, ordinal, _wrappedEntity.Entity, newValue);
}
internal void SetOriginalEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, object newValue)
{
ValidateState();
if (State == EntityState.Added)
{
throw EntityUtil.OriginalValuesDoesNotExist();
}
EntityState initialState = State;
object orgValue; // StateManagerValue
object oldOriginalValue; // the actual value
// Update original values list
StateManagerMemberMetadata memberMetadata = metadata.Member(ordinal);
if (FindOriginalValue(memberMetadata, userObject, out orgValue))
{
_originalValues.Remove((StateManagerValue)orgValue);
}
if (memberMetadata.IsComplex)
{
oldOriginalValue = memberMetadata.GetValue(userObject);
if (oldOriginalValue == null)
{
throw EntityUtil.NullableComplexTypesNotSupported(memberMetadata.CLayerName);
}
IExtendedDataRecord newValueRecord = newValue as IExtendedDataRecord;
if (newValueRecord != null)
{
// Requires materialization
newValue = _cache.ComplexTypeMaterializer.CreateComplex(newValueRecord, newValueRecord.DataRecordInfo, null);
}
// We only store scalar properties values in original values, so no need to search the list
// if the property being set is complex. Just get the value as an OSpace object.
ExpandComplexTypeAndAddValues(memberMetadata, oldOriginalValue, newValue, true);
}
else
{
AddOriginalValue(memberMetadata, userObject, newValue);
}
if (initialState == EntityState.Unchanged)
{
State = EntityState.Modified;
}
}
/// <summary>
/// Method called to start the change tracking process on an entity property. The current property value is cached at
/// this stage in preparation for later storage in the original values record. Multiple successful calls to this method
/// will overwrite the cached values.
/// All inputs are in OSpace.
/// </summary>
/// <param name="entityMemberName">Name of the top-level entity property that is changing</param>
/// <param name="complexObject">If entityMemberName refers to a complex property, this is the complex
/// object that contains the change. Otherwise this is null.</param>
/// <param name="complexObjectMemberName">If entityMemberName refers to a complex property, this is the name of
/// the property that is changing on complexObject. Otherwise this is null.</param>
private void EntityMemberChanging(string entityMemberName, object complexObject, string complexObjectMemberName)
{
string changingMemberName;
StateManagerTypeMetadata typeMetadata;
object changingObject;
// Get the metadata for the property that is changing, and verify that it is valid to change it for this entry
int changingOrdinal = this.GetAndValidateChangeMemberInfo(entityMemberName, complexObject, complexObjectMemberName,
out typeMetadata, out changingMemberName, out changingObject);
// if EntityKey is changing and is in a valid scenario for it to change, no further action is needed
if (changingOrdinal == -2)
{
return;
}
Debug.Assert(changingOrdinal != -1, "Expected GetAndValidateChangeMemberInfo to throw for a invalid property name");
// Cache the current value for later storage in original values. If we are not in a state where we should update
// the original values, we don't even need to bother saving the current value here. However, we will still cache
// the other data regarding the change, so that we always require matching Changing and Changed calls, regardless of the state.
StateManagerMemberMetadata memberMetadata = typeMetadata.Member(changingOrdinal);
// POCO
// Entities which don't implement IEntityWithChangeTracker entity can already have original values even in the Unchanged state.
_cache.SaveOriginalValues = (State == EntityState.Unchanged || State == EntityState.Modified) &&
!FindOriginalValue(memberMetadata, changingObject);
// devnote: Not using GetCurrentEntityValue here because change tracking can only be done on OSpace members,
// so we don't need to worry about shadow state, and we don't want a CSpace representation of complex objects
object oldValue = memberMetadata.GetValue(changingObject);
Debug.Assert(this.State != EntityState.Detached, "Change tracking should not happen on detached entities.");
SetCachedChangingValues(entityMemberName, changingObject, changingMemberName, this.State, oldValue);
}
// helper method used to get value of property
internal object GetOriginalEntityValue(string memberName)
{
int ordinal = _cacheTypeMetadata.GetOrdinalforOLayerMemberName(memberName);
return GetOriginalEntityValue(_cacheTypeMetadata, ordinal, _wrappedEntity.Entity, ObjectStateValueRecord.OriginalReadonly);
}
internal object GetOriginalEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord)
{
Debug.Assert(updatableRecord != ObjectStateValueRecord.OriginalUpdatablePublic, "OriginalUpdatablePublic records must preserve complex type information, use the overload that takes parentEntityPropertyIndex");
return GetOriginalEntityValue(metadata, ordinal, userObject, updatableRecord, s_EntityRoot);
}
internal object GetOriginalEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord, int parentEntityPropertyIndex)
{
// if original value is stored, then use it, otherwise use the current value from the entity
ValidateState();
object retValue;
StateManagerMemberMetadata member = metadata.Member(ordinal);
if (FindOriginalValue(member, userObject, out retValue))
{
// If the object is null, return DBNull.Value to be consistent with GetCurrentEntityValue
return ((StateManagerValue)retValue).originalValue ?? DBNull.Value;
}
return GetCurrentEntityValue(metadata, ordinal, userObject, updatableRecord, parentEntityPropertyIndex);
}
internal object GetCurrentEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord)
{
Debug.Assert(updatableRecord != ObjectStateValueRecord.OriginalUpdatablePublic, "OriginalUpdatablePublic records must preserve complex type information, use the overload that takes parentEntityPropertyIndex");
return GetCurrentEntityValue(metadata, ordinal, userObject, updatableRecord, s_EntityRoot);
}
internal object GetCurrentEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, ObjectStateValueRecord updatableRecord, int parentEntityPropertyIndex)
{
ValidateState();
object retValue = null;
StateManagerMemberMetadata member = metadata.Member(ordinal);
Debug.Assert(null != member, "didn't throw ArgumentOutOfRangeException");
if (!metadata.IsMemberPartofShadowState(ordinal))
{ // if it is not shadow state
retValue = member.GetValue(userObject);
// Wrap the value in a record if it is a non-null complex type
if (member.IsComplex && retValue != null)
{
// need to get the new StateManagerTypeMetadata for nested /complext member
switch (updatableRecord)
{
case ObjectStateValueRecord.OriginalReadonly:
retValue = new ObjectStateEntryDbDataRecord(this,
_cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType), retValue);
break;
case ObjectStateValueRecord.CurrentUpdatable:
retValue = new ObjectStateEntryDbUpdatableDataRecord(this,
_cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType), retValue);
break;
case ObjectStateValueRecord.OriginalUpdatableInternal:
retValue = new ObjectStateEntryOriginalDbUpdatableDataRecord_Internal(this,
_cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType), retValue);
break;
case ObjectStateValueRecord.OriginalUpdatablePublic:
retValue = new ObjectStateEntryOriginalDbUpdatableDataRecord_Public(this,
_cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType), retValue, parentEntityPropertyIndex);
break;
default:
Debug.Assert(false, "shouldn't happen");
break;
}
// we need to pass the toplevel ordinal
}
}
#if DEBUG // performance, don't do this work in retail until shadow state is supported
else if (userObject == _wrappedEntity.Entity)
{
Debug.Assert(false, "shadowstate not supported");
#if SupportShadowState
Debug.Assert(null != _currentValues, "shadow state without values");
_currentValues.TryGetValue(member.CLayerName, out retValue); // try to get it from shadow state if exists
// we don't support CSpace only complex type
#endif
}
#endif
return retValue ?? DBNull.Value;
}
private bool FindOriginalValue(StateManagerMemberMetadata metadata, object instance)
{
object tmp;
return FindOriginalValue(metadata, instance, out tmp);
}
internal bool FindOriginalValue(StateManagerMemberMetadata metadata, object instance, out object value)
{
bool found = false;
object retValue = null;
if (null != _originalValues)
{
foreach (StateManagerValue cachevalue in _originalValues) // this should include also shadow state
{
if (cachevalue.userObject == instance && cachevalue.memberMetadata == metadata)
{
found = true;
retValue = cachevalue;
break;
}
}
}
value = retValue;
return found;
}
// Get AssociationEndMember of current entry of given relationship
// Relationship must be related to the current entry.
internal AssociationEndMember GetAssociationEndMember(RelationshipEntry relationshipEntry)
{
Debug.Assert((object)this.EntityKey != null, "entry should have a not null EntityKey");
ValidateState();
AssociationEndMember endMember = relationshipEntry.RelationshipWrapper.GetAssociationEndMember(EntityKey);
Debug.Assert(null != endMember, "should be one of the ends of the relationship");
return endMember;
}
// Get entry which is on the other end of given relationship.
// Relationship must be related to the current entry.
internal EntityEntry GetOtherEndOfRelationship(RelationshipEntry relationshipEntry)
{
Debug.Assert((object)this.EntityKey != null, "entry should have a not null EntityKey");
return _cache.GetEntityEntry(relationshipEntry.RelationshipWrapper.GetOtherEntityKey(this.EntityKey));
}
/// <summary>
/// Helper method to recursively expand a complex object's values down to scalars for storage in the original values record.
/// This method is used when a whole complex object is set on its parent object, instead of just setting
/// individual scalar values on that object.
/// </summary>
/// <param name="memberMetadata">metadata for the complex property being expanded on the parent
/// where the parent can be an entity or another complex object</param>
/// <param name="oldComplexObject">Old value of the complex property. Scalar values from this object are stored in the original values record</param>
/// <param name="newComplexObject">New value of the complex property. This object reference is used in the original value record and is
/// associated with the scalar values for the same property on the oldComplexObject</param>
/// <param name="useOldComplexObject">Whether or not to use the existing complex object in the original values or to use the original value that is already present </param>
private void ExpandComplexTypeAndAddValues(StateManagerMemberMetadata memberMetadata, object oldComplexObject, object newComplexObject, bool useOldComplexObject)
{
Debug.Assert(memberMetadata.IsComplex, "Cannot expand non-complex objects");
if (newComplexObject == null)
{
throw EntityUtil.NullableComplexTypesNotSupported(memberMetadata.CLayerName);
}
Debug.Assert(oldComplexObject == null || (oldComplexObject.GetType() == newComplexObject.GetType()), "Cannot replace a complex object with an object of a different type, unless the original one was null");
StateManagerTypeMetadata typeMetadata = _cache.GetOrAddStateManagerTypeMetadata(memberMetadata.CdmMetadata.TypeUsage.EdmType);
object retValue;
for (int field = 0; field < typeMetadata.FieldCount; field++)
{
StateManagerMemberMetadata complexMemberMetadata = typeMetadata.Member(field);
if (complexMemberMetadata.IsComplex)
{
object oldComplexMemberValue = null;
if (oldComplexObject != null)
{
oldComplexMemberValue = complexMemberMetadata.GetValue(oldComplexObject);
if (oldComplexMemberValue == null && FindOriginalValue(complexMemberMetadata, oldComplexObject, out retValue))
{
_originalValues.Remove((StateManagerValue)retValue);
}
}
ExpandComplexTypeAndAddValues(complexMemberMetadata, oldComplexMemberValue, complexMemberMetadata.GetValue(newComplexObject), useOldComplexObject);
}
else
{
object originalValue = null;
object complexObject = newComplexObject;
if (useOldComplexObject)
{
// Set the original values using the existing current value object
// complexObject --> the existing complex object
// originalValue --> the new value to set for this member
originalValue = complexMemberMetadata.GetValue(newComplexObject);
complexObject = oldComplexObject;
}
else
{
if (oldComplexObject != null)
{
// If we already have an entry for this property in the original values list, we need to remove it. We can't just
// update it because StateManagerValue is a struct and there is no way to get a reference to the entry in the list.
originalValue = complexMemberMetadata.GetValue(oldComplexObject);
if (FindOriginalValue(complexMemberMetadata, oldComplexObject, out retValue))
{
StateManagerValue originalStateValue = ((StateManagerValue)retValue);
_originalValues.Remove(originalStateValue);
originalValue = originalStateValue.originalValue;
}
else
{
Debug.Assert(this.Entity is IEntityWithChangeTracker, "for POCO objects the snapshot should contain all original values");
}
}
else
{
originalValue = complexMemberMetadata.GetValue(newComplexObject);
}
}
// Add the new entry. The userObject will reference the new complex object that is currently being set.
// If the value was in the list previously, we will still use the old value with the new object reference.
// That will ensure that we preserve the old value while still maintaining the link to the
// existing complex object that is attached to the entity or parent complex object. If an entry is already
// in the list this means that it was either explicitly set by the user or the entire complex type was previously
// set and expanded down to the individual properties. In either case we do the same thing.
AddOriginalValue(complexMemberMetadata, complexObject, originalValue);
}
}
}
/// <summary>
/// Helper method to validate that the property names being reported as changing/changed are valid for this entity and that
/// the entity is in a valid state for the change request. Also determines if this is a change on a complex object, and
/// returns the appropriate metadata and object to be used for the rest of the changing and changed operations.
/// </summary>
/// <param name="entityMemberName">Top-level entity property name</param>
/// <param name="complexObject">Complex object that contains the change, null if the change is on a top-level entity property</param>
/// <param name="complexObjectMemberName">Name of the property that is changing on the complexObject, null for top-level entity properties</param>
/// <param name="typeMetadata">Metadata for the type that contains the change, either for the entity itself or for the complex object</param>
/// <param name="changingMemberName">Property name that is actually changing -- either entityMemberName for entities or
/// complexObjectMemberName for complex objects</param>
/// <param name="changingObject">Object reference that contains the change, either the entity or complex object
/// as appropriate for the requested change</param>
/// <returns>Ordinal of the property that is changing, or -2 if the EntityKey is changing in a valid scenario. This is relative
/// to the returned typeMetadata. Throws exceptions if the requested property name(s) are invalid for this entity.</returns>
internal int GetAndValidateChangeMemberInfo(string entityMemberName, object complexObject, string complexObjectMemberName,
out StateManagerTypeMetadata typeMetadata, out string changingMemberName, out object changingObject)
{
typeMetadata = null;
changingMemberName = null;
changingObject = null;
EntityUtil.CheckArgumentNull(entityMemberName, "entityMemberName");
// complexObject and complexObjectMemberName are allowed to be null here for change tracking on top-level entity properties
ValidateState();
int changingOrdinal = _cacheTypeMetadata.GetOrdinalforOLayerMemberName(entityMemberName);
if (changingOrdinal == -1)
{
if (entityMemberName == StructuralObject.EntityKeyPropertyName)
{
// Setting EntityKey property is only allowed from here when we are in the middle of relationship fixup.
if (!_cache.InRelationshipFixup)
{
throw EntityUtil.CantSetEntityKey();
}
else
{
// If we are in fixup, there is nothing more to do here with EntityKey, so just
// clear the saved changing values and return. This will ensure that we behave
// the same with the change notifications on EntityKey as with other properties.
// I.e. we still don't allow the following:
// EntityMemberChanging("Property1")
// EntityMemberChanging("EntityKey")
// EntityMemberChanged("EntityKey")
// EntityMemberChanged("Property1")
Debug.Assert(this.State != EntityState.Detached, "Change tracking should not happen on detached entities.");
SetCachedChangingValues(null, null, null, this.State, null);
return -2;
}
}
else
{
throw EntityUtil.ChangeOnUnmappedProperty(entityMemberName);
}
}
else
{
StateManagerTypeMetadata tmpTypeMetadata;
string tmpChangingMemberName;
object tmpChangingObject;
// entityMemberName is a confirmed valid property on the Entity, but if this is a complex type we also need to validate its property
if (complexObject != null)
{
// a complex object was provided, but the top-level Entity property is not complex
if (!_cacheTypeMetadata.Member(changingOrdinal).IsComplex)
{
throw EntityUtil.ComplexChangeRequestedOnScalarProperty(entityMemberName);
}
tmpTypeMetadata = _cache.GetOrAddStateManagerTypeMetadata(complexObject.GetType(), (EntitySet)this.EntitySet);
changingOrdinal = tmpTypeMetadata.GetOrdinalforOLayerMemberName(complexObjectMemberName);
if (changingOrdinal == -1)
{
throw EntityUtil.ChangeOnUnmappedComplexProperty(complexObjectMemberName);
}
tmpChangingMemberName = complexObjectMemberName;
tmpChangingObject = complexObject;
}
else
{
tmpTypeMetadata = _cacheTypeMetadata;
tmpChangingMemberName = entityMemberName;
tmpChangingObject = this.Entity;
if (WrappedEntity.IdentityType != Entity.GetType() && // Is a proxy
Entity is IEntityWithChangeTracker && // Is a full proxy
IsPropertyAForeignKey(entityMemberName)) // Property is part of FK
{
// Set a flag so that we don't try to set FK properties while already in a setter.
_cache.EntityInvokingFKSetter = WrappedEntity.Entity;
}
}
VerifyEntityValueIsEditable(tmpTypeMetadata, changingOrdinal, tmpChangingMemberName);
typeMetadata = tmpTypeMetadata;
changingMemberName = tmpChangingMemberName;
changingObject = tmpChangingObject;
return changingOrdinal;
}
}
/// <summary>
/// Helper method to set the information needed for the change tracking cache. Ensures that all of these values get set together.
/// </summary>
private void SetCachedChangingValues(string entityMemberName, object changingObject, string changingMember, EntityState changingState, object oldValue)
{
_cache.ChangingEntityMember = entityMemberName;
_cache.ChangingObject = changingObject;
_cache.ChangingMember = changingMember;
_cache.ChangingState = changingState;
_cache.ChangingOldValue = oldValue;
if (changingState == EntityState.Detached)
{
_cache.SaveOriginalValues = false;
}
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)] // don't have debugger view expand this
internal OriginalValueRecord EditableOriginalValues
{
get
{
Debug.Assert(!this.IsKeyEntry, "should not edit original key entry");
Debug.Assert(EntityState.Modified == State ||
EntityState.Deleted == State ||
EntityState.Unchanged == State,
"only expecting Modified or Deleted state");
return new ObjectStateEntryOriginalDbUpdatableDataRecord_Internal(this, _cacheTypeMetadata, _wrappedEntity.Entity);
}
}
internal void DetachObjectStateManagerFromEntity()
{
// This method can be called on relationship entries where there is no entity
if (!this.IsKeyEntry) // _wrappedEntity.Entity is not null.
{
_wrappedEntity.SetChangeTracker(null);
_wrappedEntity.DetachContext();
if (!this._cache.TransactionManager.IsAttachTracking ||
this._cache.TransactionManager.OriginalMergeOption != MergeOption.NoTracking)
{
// If AttachTo() failed while attaching graph retrieved with NoTracking option,
// we don't want to reset the EntityKey
//Entry's this._entityKey is set to null at the caller, maintaining consistency between entityWithKey.EntityKey and this.EntityKey
_wrappedEntity.EntityKey = null;
}
}
}
// This method is used for entities which don't implement IEntityWithChangeTracker to store orignal values of properties
// which are later used to detect changes in properties
internal void TakeSnapshot(bool onlySnapshotComplexProperties)
{
Debug.Assert(!this.IsKeyEntry);
if (this.State != EntityState.Added)
{
StateManagerTypeMetadata metadata = this._cacheTypeMetadata;
int fieldCount = this.GetFieldCount(metadata);
object currentValue;
for (int i = 0; i < fieldCount; i++)
{
StateManagerMemberMetadata member = metadata.Member(i);
if (member.IsComplex)
{
// memberValue is a complex object
currentValue = member.GetValue(this._wrappedEntity.Entity);
this.AddComplexObjectSnapshot(this.Entity, i, currentValue);
this.TakeSnapshotOfComplexType(member, currentValue);
}
else if (!onlySnapshotComplexProperties)
{
currentValue = member.GetValue(this._wrappedEntity.Entity);
this.AddOriginalValue(member, this._wrappedEntity.Entity, currentValue);
}
}
}
this.TakeSnapshotOfForeignKeys();
}
internal void TakeSnapshotOfForeignKeys()
{
Dictionary<RelatedEnd, HashSet<EntityKey>> keys;
this.FindRelatedEntityKeysByForeignKeys(out keys, useOriginalValues: false);
if (keys != null)
{
foreach (var pair in keys)
{
EntityReference reference = pair.Key as EntityReference;
Debug.Assert(reference != null, "EntityReference expected");
Debug.Assert(pair.Value.Count == 1, "Unexpected number of keys");
if (!ForeignKeyFactory.IsConceptualNullKey(reference.CachedForeignKey))
{
reference.SetCachedForeignKey(pair.Value.First(), this);
}
}
}
}
private void TakeSnapshotOfComplexType(StateManagerMemberMetadata member, object complexValue)
{
Debug.Assert(member.IsComplex, "Cannot expand non-complex objects");
// Skip null values
if (complexValue == null)
return;
StateManagerTypeMetadata typeMetadata = _cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType);
for (int i = 0; i < typeMetadata.FieldCount; i++)
{
StateManagerMemberMetadata complexMember = typeMetadata.Member(i);
object currentValue = complexMember.GetValue(complexValue);
if (complexMember.IsComplex)
{
// Recursive call for nested complex types
// For POCO objects we have to store a reference to the original complex object
this.AddComplexObjectSnapshot(complexValue, i, currentValue);
TakeSnapshotOfComplexType(complexMember, currentValue);
}
else
{
if (!FindOriginalValue(complexMember, complexValue))
{
AddOriginalValue(complexMember, complexValue, currentValue);
}
}
}
}
private void AddComplexObjectSnapshot(object userObject, int ordinal, object complexObject)
{
Debug.Assert(userObject != null, "null userObject");
Debug.Assert(ordinal >= 0, "invalid ordinal");
if (complexObject == null)
{
return;
}
// Verify if the same complex object is not used multiple times.
this.CheckForDuplicateComplexObjects(complexObject);
if (this._originalComplexObjects == null)
{
this._originalComplexObjects = new Dictionary<object, Dictionary<int, object>>();
}
Dictionary<int, object> ordinal2complexObject;
if (!this._originalComplexObjects.TryGetValue(userObject, out ordinal2complexObject))
{
ordinal2complexObject = new Dictionary<int, object>();
this._originalComplexObjects.Add(userObject, ordinal2complexObject);
}
Debug.Assert(!ordinal2complexObject.ContainsKey(ordinal), "shouldn't contain this ordinal yet");
ordinal2complexObject.Add(ordinal, complexObject);
}
private void CheckForDuplicateComplexObjects(object complexObject)
{
if (this._originalComplexObjects == null || complexObject == null)
return;
foreach (Dictionary<int, object> ordinal2complexObject in this._originalComplexObjects.Values)
{
foreach (object oldComplexObject in ordinal2complexObject.Values)
{
if (Object.ReferenceEquals(complexObject, oldComplexObject))
{
throw new InvalidOperationException(System.Data.Entity.Strings.ObjectStateEntry_ComplexObjectUsedMultipleTimes(this.Entity.GetType().FullName, complexObject.GetType().FullName));
}
}
}
}
/// <summary>
/// Uses DetectChanges to determine whether or not the current value of the property with the given
/// name is different from its original value. Note that this may be different from the property being
/// marked as modified since a property which has not changed can still be marked as modified.
/// </summary>
/// <remarks>
/// For complex properties, a new instance of the complex object which has all the same property
/// values as the original instance is not considered to be different by this method.
/// </remarks>
/// <param name="propertyName">The name of the property.</param>
/// <returns>True if the property has changed; false otherwise.</returns>
public override bool IsPropertyChanged(string propertyName)
{
return DetectChangesInProperty(ValidateAndGetOrdinalForProperty(propertyName, "IsPropertyChanged"),
detectOnlyComplexProperties: false, detectOnly: true);
}
private bool DetectChangesInProperty(int ordinal, bool detectOnlyComplexProperties, bool detectOnly)
{
bool changeDetected = false;
StateManagerMemberMetadata member = _cacheTypeMetadata.Member(ordinal);
var currentValue = member.GetValue(this._wrappedEntity.Entity);
if (member.IsComplex)
{
if (this.State != EntityState.Deleted)
{
var oldComplexValue = this.GetComplexObjectSnapshot(this.Entity, ordinal);
bool complexObjectInstanceChanged = this.DetectChangesInComplexType(member, member, currentValue, oldComplexValue, ref changeDetected, detectOnly);
if (complexObjectInstanceChanged)
{
// instance of complex object was changed
// Before updating the snapshot verify if the same complex object is not used multiple times.
this.CheckForDuplicateComplexObjects(currentValue);
if (!detectOnly)
{
// equivalent of EntityObject.ReportPropertyChanging()
((IEntityChangeTracker)this).EntityMemberChanging(member.CLayerName);
Debug.Assert(_cache.SaveOriginalValues, "complex object instance was changed so the SaveOriginalValues flag should be set to true");
// Since the EntityMemberChanging method is called AFTER the complex object was changed, it means that
// the EntityMemberChanging method was unable to find the real oldValue.
// The real old value is stored for POCO objects in _originalComplexObjects dictionary.
// The cached changing oldValue has to be updated with the real oldValue.
_cache.ChangingOldValue = oldComplexValue;
// equivalent of EntityObject.ReportPropertyChanged()
((IEntityChangeTracker)this).EntityMemberChanged(member.CLayerName);
}
// The _originalComplexObjects should always contain references to the values of complex objects which are "original"
// at the moment of calling GetComplexObjectSnapshot(). They are used to get original scalar values from _originalValues.
this.UpdateComplexObjectSnapshot(member, this.Entity, ordinal, currentValue);
if (!changeDetected)
{
// If we haven't already detected a change then we need to check the properties of the complex
// object to see if there are any changes so that IsPropertyChanged will not skip reporting the
// change just because the object reference has changed.
DetectChangesInComplexType(member, member, currentValue, oldComplexValue, ref changeDetected, detectOnly);
}
}
}
}
else if (!detectOnlyComplexProperties)
{
object originalStateManagerValue;
var originalValueFound = this.FindOriginalValue(member, this._wrappedEntity.Entity, out originalStateManagerValue);
Debug.Assert(originalValueFound, "Original value not found even after snapshot.");
var originalValue = ((StateManagerValue)originalStateManagerValue).originalValue;
if (!Object.Equals(currentValue, originalValue))
{
changeDetected = true;
// Key property - throw if the actual byte values have changed, otherwise ignore the change
if (member.IsPartOfKey)
{
if (!ByValueEqualityComparer.Default.Equals(currentValue, originalValue))
{
throw EntityUtil.CannotModifyKeyProperty(member.CLayerName);
}
}
else
{
if (this.State != EntityState.Deleted && !detectOnly)
{
// equivalent of EntityObject.ReportPropertyChanging()
((IEntityChangeTracker)this).EntityMemberChanging(member.CLayerName);
// equivalent of EntityObject.ReportPropertyChanged()
((IEntityChangeTracker)this).EntityMemberChanged(member.CLayerName);
}
}
}
}
return changeDetected;
}
// This method uses original values stored in the ObjectStateEntry to detect changes in values of entity's properties
internal void DetectChangesInProperties(bool detectOnlyComplexProperties)
{
Debug.Assert(!this.IsKeyEntry, "Entry should be an EntityEntry");
Debug.Assert(this.State != EntityState.Added, "This method should not be called for entries in Added state");
int fieldCount = GetFieldCount(_cacheTypeMetadata);
for (int i = 0; i < fieldCount; i++)
{
DetectChangesInProperty(i, detectOnlyComplexProperties, detectOnly: false);
}
}
private bool DetectChangesInComplexType(
StateManagerMemberMetadata topLevelMember,
StateManagerMemberMetadata complexMember,
object complexValue,
object oldComplexValue,
ref bool changeDetected,
bool detectOnly)
{
Debug.Assert(complexMember.IsComplex, "Cannot expand non-complex objects");
if (complexValue == null)
{
// If the values are just null, do not detect this as a change
if (oldComplexValue == null)
{
return false;
}
throw EntityUtil.NullableComplexTypesNotSupported(complexMember.CLayerName);
}
if (!Object.ReferenceEquals(oldComplexValue, complexValue))
{
// Complex object instance was changed. The calling method will update the snapshot of this object.
return true;
}
Debug.Assert(oldComplexValue != null, "original complex type value should not be null at this point");
StateManagerTypeMetadata metadata = _cache.GetOrAddStateManagerTypeMetadata(complexMember.CdmMetadata.TypeUsage.EdmType);
for (int i = 0; i < GetFieldCount(metadata); i++)
{
StateManagerMemberMetadata member = metadata.Member(i);
object currentValue = null;
currentValue = member.GetValue(complexValue);
if (member.IsComplex)
{
if (this.State != EntityState.Deleted)
{
var oldNestedComplexValue = this.GetComplexObjectSnapshot(complexValue, i);
bool complexObjectInstanceChanged = DetectChangesInComplexType(topLevelMember, member, currentValue, oldNestedComplexValue, ref changeDetected, detectOnly);
if (complexObjectInstanceChanged)
{
// instance of complex object was changed
// Before updating the snapshot verify if the same complex object is not used multiple times.
this.CheckForDuplicateComplexObjects(currentValue);
if (!detectOnly)
{
// equivalent of EntityObject.ReportComplexPropertyChanging()
((IEntityChangeTracker)this).EntityComplexMemberChanging(topLevelMember.CLayerName, complexValue, member.CLayerName);
// Since the EntityComplexMemberChanging method is called AFTER the complex object was changed, it means that
// the EntityComplexMemberChanging method was unable to find real oldValue.
// The real old value is stored for POCO objects in _originalComplexObjects dictionary.
// The cached changing oldValue has to be updated with the real oldValue.
_cache.ChangingOldValue = oldNestedComplexValue;
// equivalent of EntityObject.ReportComplexPropertyChanged()
((IEntityChangeTracker)this).EntityComplexMemberChanged(topLevelMember.CLayerName, complexValue, member.CLayerName);
}
// The _originalComplexObjects should always contain references to the values of complex objects which are "original"
// at the moment of calling GetComplexObjectSnapshot(). They are used to get original scalar values from _originalValues.
this.UpdateComplexObjectSnapshot(member, complexValue, i, currentValue);
if (!changeDetected)
{
DetectChangesInComplexType(topLevelMember, member, currentValue, oldNestedComplexValue, ref changeDetected, detectOnly);
}
}
}
}
else
{
object originalStateManagerValue;
bool originalValueFound = FindOriginalValue(member, complexValue, out originalStateManagerValue);
// originalValueFound will be false if the complex value was initially null since then its original
// values will always be null, in which case all original scalar properties of the complex value are
// considered null.
if (!Object.Equals(currentValue, originalValueFound ? ((StateManagerValue)originalStateManagerValue).originalValue : null))
{
changeDetected = true;
Debug.Assert(!member.IsPartOfKey, "Found member of complex type that is part of a key");
if (!detectOnly)
{
// equivalent of EntityObject.ReportComplexPropertyChanging()
((IEntityChangeTracker)this).EntityComplexMemberChanging(topLevelMember.CLayerName, complexValue, member.CLayerName);
// equivalent of EntityObject.ReportComplexPropertyChanged()
((IEntityChangeTracker)this).EntityComplexMemberChanged(topLevelMember.CLayerName, complexValue, member.CLayerName);
}
}
}
}
// Scalar value in a complex object was changed
return false;
}
private object GetComplexObjectSnapshot(object parentObject, int parentOrdinal)
{
object oldComplexObject = null;
if (this._originalComplexObjects != null)
{
Dictionary<int, object> ordinal2complexObject;
if (this._originalComplexObjects.TryGetValue(parentObject, out ordinal2complexObject))
{
ordinal2complexObject.TryGetValue(parentOrdinal, out oldComplexObject);
}
}
return oldComplexObject;
}
// The _originalComplexObjects should always contain references to the values of complex objects which are "original"
// at the moment of calling GetComplexObjectSnapshot(). They are used to get original scalar values from _originalValues
// and to check if complex object instance was changed.
// This method should be called after EntityMemberChanged in POCO case.
internal void UpdateComplexObjectSnapshot(StateManagerMemberMetadata member, object userObject, int ordinal, object currentValue)
{
bool requiresAdd = true;
if (this._originalComplexObjects != null)
{
Dictionary<int, object> ordinal2complexObject;
if (this._originalComplexObjects.TryGetValue(userObject, out ordinal2complexObject))
{
Debug.Assert(ordinal2complexObject != null, "value should already exists");
object oldValue;
ordinal2complexObject.TryGetValue(ordinal, out oldValue);
// oldValue may be null if the complex object was attached with a null value
ordinal2complexObject[ordinal] = currentValue;
// check nested complex objects (if they exist)
if (oldValue != null && this._originalComplexObjects.TryGetValue(oldValue, out ordinal2complexObject))
{
this._originalComplexObjects.Remove(oldValue);
this._originalComplexObjects.Add(currentValue, ordinal2complexObject);
StateManagerTypeMetadata typeMetadata = _cache.GetOrAddStateManagerTypeMetadata(member.CdmMetadata.TypeUsage.EdmType);
for (int i = 0; i < typeMetadata.FieldCount; i++)
{
StateManagerMemberMetadata complexMember = typeMetadata.Member(i);
if (complexMember.IsComplex)
{
object nestedValue = complexMember.GetValue(currentValue);
// Recursive call for nested complex objects
UpdateComplexObjectSnapshot(complexMember, currentValue, i, nestedValue);
}
}
}
requiresAdd = false;
}
}
if(requiresAdd)
{
AddComplexObjectSnapshot(userObject, ordinal, currentValue);
}
}
/// <summary>
/// Processes each dependent end of an FK relationship in this entity and determines if a nav
/// prop is set to a principal. If it is, and if the principal is Unchanged or Modified,
/// then the primary key value is taken from the principal and used to fixup the FK value.
/// This is called during AddObject so that references set from the added object will take
/// precedence over FK values such that there is no need for the user to set FK values
/// explicitly. If a conflict in the FK value is encountered due to an overlapping FK
/// that is tied to two different PK values, then an exception is thrown.
/// Note that references to objects that are not yet tracked by the context are ignored, since
/// they will ultimately be brought into the context as Added objects, at which point we would
/// have skipped them anyway because the are not Unchanged or Modified.
/// </summary>
internal void FixupFKValuesFromNonAddedReferences()
{
Debug.Assert(EntitySet is EntitySet, "Expect entity entries to have true entity sets.");
if (!((EntitySet)EntitySet).HasForeignKeyRelationships)
{
return;
}
// Keep track of all FK values that have already been set so that we can detect conflicts.
var changedFKs = new Dictionary<int, object>();
foreach (Tuple<AssociationSet, ReferentialConstraint> dependent in ForeignKeyDependents)
{
var reference = RelationshipManager.GetRelatedEndInternal(dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name) as EntityReference;
Debug.Assert(reference != null, "Expected reference to exist and be an entity reference (not collection)");
if (reference.TargetAccessor.HasProperty)
{
var principal = WrappedEntity.GetNavigationPropertyValue(reference);
if (principal != null)
{
ObjectStateEntry principalEntry;
if (_cache.TryGetObjectStateEntry(principal, out principalEntry) &&
(principalEntry.State == EntityState.Modified || principalEntry.State == EntityState.Unchanged))
{
Debug.Assert(principalEntry is EntityEntry, "Existing entry for an entity must be an EntityEntry, not a RelationshipEntry");
reference.UpdateForeignKeyValues(WrappedEntity, ((EntityEntry)principalEntry).WrappedEntity, changedFKs, forceChange: false);
}
}
}
}
}
// Method used for entities which don't implement IEntityWithRelationships
internal void TakeSnapshotOfRelationships()
{
Debug.Assert(this._wrappedEntity != null, "wrapped entity shouldn't be null");
Debug.Assert(!(this._wrappedEntity.Entity is IEntityWithRelationships), "this method should be called only for entities which don't implement IEntityWithRelationships");
RelationshipManager rm = this._wrappedEntity.RelationshipManager;
StateManagerTypeMetadata metadata = this._cacheTypeMetadata;
ReadOnlyMetadataCollection<NavigationProperty> navigationProperties =
(metadata.CdmMetadata.EdmType as EntityType).NavigationProperties;
foreach (NavigationProperty n in navigationProperties)
{
RelatedEnd relatedEnd = rm.GetRelatedEndInternal(n.RelationshipType.FullName, n.ToEndMember.Name);
object val = this.WrappedEntity.GetNavigationPropertyValue(relatedEnd);
if (val != null)
{
if (n.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
{
// Collection
IEnumerable collection = val as IEnumerable;
if (collection == null)
{
throw new EntityException(System.Data.Entity.Strings.ObjectStateEntry_UnableToEnumerateCollection(n.Name, this.Entity.GetType().FullName));
}
foreach (object o in collection)
{
// Skip nulls in collections
if (o != null)
{
this.TakeSnapshotOfSingleRelationship(relatedEnd, n, o);
}
}
}
else
{
// Reference
this.TakeSnapshotOfSingleRelationship(relatedEnd, n, val);
}
}
}
}
private void TakeSnapshotOfSingleRelationship(RelatedEnd relatedEnd, NavigationProperty n, object o)
{
// Related entity can be already attached, so find the existing entry
EntityEntry relatedEntry = this.ObjectStateManager.FindEntityEntry(o);
IEntityWrapper relatedWrapper;
if (relatedEntry != null)
{
Debug.Assert(this.ObjectStateManager.TransactionManager.IsAddTracking ||
this.ObjectStateManager.TransactionManager.IsAttachTracking, "Should be inside Attach or Add");
//relatedEntry.VerifyOrUpdateRelatedEnd(n, this._wrappedEntity);
relatedWrapper = relatedEntry._wrappedEntity;
// In case of unidirectional relationships, it is possible that the other end of relationship was already added
// to the context but its relationship manager doesn't contain proper related end with the current entity.
// In OSM we treat all relationships as bidirectional so the related end has to be updated.
RelatedEnd otherRelatedEnd = relatedWrapper.RelationshipManager.GetRelatedEndInternal(n.RelationshipType.FullName, n.FromEndMember.Name);
if (!otherRelatedEnd.ContainsEntity(this._wrappedEntity))
{
Debug.Assert(relatedWrapper.ObjectStateEntry != null, "Expected related entity to be tracked in snapshot code.");
if (relatedWrapper.ObjectStateEntry.State == EntityState.Deleted)
{
throw EntityUtil.UnableToAddRelationshipWithDeletedEntity();
}
if (ObjectStateManager.TransactionManager.IsAttachTracking &&
(State & (EntityState.Modified | EntityState.Unchanged)) != 0 &&
(relatedWrapper.ObjectStateEntry.State & (EntityState.Modified | EntityState.Unchanged)) != 0)
{
EntityEntry principalEntry = null;
EntityEntry dependentEntry = null;
if (relatedEnd.IsDependentEndOfReferentialConstraint(checkIdentifying: false))
{
principalEntry = relatedWrapper.ObjectStateEntry;
dependentEntry = this;
}
else if (otherRelatedEnd.IsDependentEndOfReferentialConstraint(checkIdentifying: false))
{
principalEntry = this;
dependentEntry = relatedWrapper.ObjectStateEntry;
}
if (principalEntry != null)
{
var constraint = ((AssociationType)relatedEnd.RelationMetadata).ReferentialConstraints[0];
if (!RelatedEnd.VerifyRIConstraintsWithRelatedEntry(constraint, dependentEntry.GetCurrentEntityValue, principalEntry.EntityKey))
{
throw EntityUtil.InconsistentReferentialConstraintProperties();
}
}
}
// Keep track of the fact that we aligned the related end here so that we can undo
// it in rollback without wiping the already existing nav properties.
EntityReference otherEndAsRef = otherRelatedEnd as EntityReference;
if (otherEndAsRef != null && otherEndAsRef.NavigationPropertyIsNullOrMissing())
{
ObjectStateManager.TransactionManager.AlignedEntityReferences.Add(otherEndAsRef);
}
otherRelatedEnd.AddToLocalCache(this._wrappedEntity, applyConstraints: true);
otherRelatedEnd.OnAssociationChanged(CollectionChangeAction.Add, _wrappedEntity.Entity);
}
}
else
{
if (!this.ObjectStateManager.TransactionManager.WrappedEntities.TryGetValue(o, out relatedWrapper))
{
relatedWrapper = EntityWrapperFactory.WrapEntityUsingStateManager(o, this.ObjectStateManager);
}
}
if (!relatedEnd.ContainsEntity(relatedWrapper))
{
relatedEnd.AddToLocalCache(relatedWrapper, true);
relatedEnd.OnAssociationChanged(CollectionChangeAction.Add, relatedWrapper.Entity);
}
}
internal void DetectChangesInRelationshipsOfSingleEntity()
{
Debug.Assert(!this.IsKeyEntry, "Entry should be an EntityEntry");
Debug.Assert(!(this.Entity is IEntityWithRelationships), "Entity shouldn't implement IEntityWithRelationships");
StateManagerTypeMetadata metadata = this._cacheTypeMetadata;
ReadOnlyMetadataCollection<NavigationProperty> navigationProperties =
(metadata.CdmMetadata.EdmType as EntityType).NavigationProperties;
foreach (NavigationProperty n in navigationProperties)
{
RelatedEnd relatedEnd = this.WrappedEntity.RelationshipManager.GetRelatedEndInternal(n.RelationshipType.FullName, n.ToEndMember.Name);
Debug.Assert(relatedEnd != null, "relatedEnd is null");
object val = this.WrappedEntity.GetNavigationPropertyValue(relatedEnd);
HashSet<object> current = new HashSet<object>();
if (val != null)
{
if (n.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
{
// Collection
IEnumerable collection = val as IEnumerable;
if (collection == null)
{
throw new EntityException(System.Data.Entity.Strings.ObjectStateEntry_UnableToEnumerateCollection(n.Name, this.Entity.GetType().FullName));
}
foreach (object o in collection)
{
// Skip nulls in collections
if (o != null)
{
current.Add(o);
}
}
}
else
{
// Reference
current.Add(val);
}
}
// find deleted entities
foreach (object o in relatedEnd.GetInternalEnumerable())
{
if (!current.Contains(o))
{
this.AddRelationshipDetectedByGraph(
this.ObjectStateManager.TransactionManager.DeletedRelationshipsByGraph, o, relatedEnd, verifyForAdd:false);
}
else
{
current.Remove(o);
}
}
// "current" contains now only added entities
foreach (object o in current)
{
this.AddRelationshipDetectedByGraph(
this.ObjectStateManager.TransactionManager.AddedRelationshipsByGraph, o, relatedEnd, verifyForAdd:true);
}
}
}
private void AddRelationshipDetectedByGraph(
Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<IEntityWrapper>>> relationships,
object relatedObject,
RelatedEnd relatedEndFrom,
bool verifyForAdd)
{
IEntityWrapper relatedWrapper = EntityWrapperFactory.WrapEntityUsingStateManager(relatedObject, this.ObjectStateManager);
this.AddDetectedRelationship(relationships, relatedWrapper, relatedEndFrom);
RelatedEnd relatedEndTo = relatedEndFrom.GetOtherEndOfRelationship(relatedWrapper);
if (verifyForAdd &&
relatedEndTo is EntityReference &&
this.ObjectStateManager.FindEntityEntry(relatedObject) == null)
{
// If the relatedObject is not tracked by the context, let's detect it before OSM.PerformAdd to avoid
// making RelatedEnd.Add() more complicated (it would have to know when the values in relatedEndTo can be overriden, and when not
relatedEndTo.VerifyNavigationPropertyForAdd(_wrappedEntity);
}
this.AddDetectedRelationship(relationships, _wrappedEntity, relatedEndTo);
}
private void AddRelationshipDetectedByForeignKey(
Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<EntityKey>>> relationships,
Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<EntityKey>>> principalRelationships,
EntityKey relatedKey,
EntityEntry relatedEntry,
RelatedEnd relatedEndFrom)
{
Debug.Assert(!relatedKey.IsTemporary, "the relatedKey was created by a method which returns only permaanent keys");
this.AddDetectedRelationship(relationships, relatedKey, relatedEndFrom);
if (relatedEntry != null)
{
IEntityWrapper relatedWrapper = relatedEntry.WrappedEntity; ;
RelatedEnd relatedEndTo = relatedEndFrom.GetOtherEndOfRelationship(relatedWrapper);
EntityKey permanentKeyOwner = this.ObjectStateManager.GetPermanentKey(relatedEntry.WrappedEntity, relatedEndTo, this.WrappedEntity);
this.AddDetectedRelationship(principalRelationships, permanentKeyOwner, relatedEndTo);
}
}
/// <summary>
/// Designed to be used by Change Detection methods to insert
/// Added/Deleted relationships into <see cref="TransactionManager"/>
/// Creates new entries in the dictionaries if required
/// </summary>
/// <typeparam name="T">IEntityWrapper or EntityKey</typeparam>
/// <param name="relationships">The set of detected relationships to add this entry to</param>
/// <param name="relatedObject">The entity the relationship points to</param>
/// <param name="relatedEnd">The related end the relationship originates from</param>
private void AddDetectedRelationship<T>(
Dictionary<IEntityWrapper, Dictionary<RelatedEnd, HashSet<T>>> relationships,
T relatedObject,
RelatedEnd relatedEnd)
{
// Update info about changes to this/from side of the relationship
Dictionary<RelatedEnd, HashSet<T>> alreadyDetectedRelationshipsFrom;
if (!relationships.TryGetValue(relatedEnd.WrappedOwner, out alreadyDetectedRelationshipsFrom))
{
alreadyDetectedRelationshipsFrom = new Dictionary<RelatedEnd, HashSet<T>>();
relationships.Add(relatedEnd.WrappedOwner, alreadyDetectedRelationshipsFrom);
}
HashSet<T> objectsInRelatedEnd;
if (!alreadyDetectedRelationshipsFrom.TryGetValue(relatedEnd, out objectsInRelatedEnd))
{
objectsInRelatedEnd = new HashSet<T>();
alreadyDetectedRelationshipsFrom.Add(relatedEnd, objectsInRelatedEnd);
}
else
{
if (relatedEnd is EntityReference)
{
Debug.Assert(objectsInRelatedEnd.Count() == 1, "unexpected number of entities for EntityReference");
T existingRelatedObject = objectsInRelatedEnd.First();
if (!Object.Equals(existingRelatedObject, relatedObject))
{
throw EntityUtil.CannotAddMoreThanOneEntityToEntityReference(
relatedEnd.RelationshipNavigation.To,
relatedEnd.RelationshipNavigation.RelationshipName);
}
}
}
objectsInRelatedEnd.Add(relatedObject);
}
/// <summary>
/// Detaches an entry and create in its place key entry if necessary
/// Removes relationships with another key entries and removes these key entries if necessary
/// </summary>
internal void Detach()
{
ValidateState();
Debug.Assert(!this.IsKeyEntry);
bool createKeyEntry = false;
RelationshipManager relationshipManager = _wrappedEntity.RelationshipManager;
Debug.Assert(relationshipManager != null, "Entity wrapper returned a null RelationshipManager");
// Key entry should be created only when current entity is not in Added state
// and if the entity is a "OneToOne" or "ZeroToOne" end of some existing relationship.
createKeyEntry =
this.State != EntityState.Added &&
this.IsOneEndOfSomeRelationship();
_cache.TransactionManager.BeginDetaching();
try
{
// Remove current entity from collections/references (on both ends of relationship)
// Relationship entries are removed from ObjectStateManager if current entity is in Added state
// or if current entity is a "Many" end of the relationship.
// NOTE In this step only relationship entries which have normal entity on the other end
// can be detached.
// NOTE In this step no Deleted relationship entries are detached.
relationshipManager.DetachEntityFromRelationships(this.State);
}
finally
{
_cache.TransactionManager.EndDetaching();
}
// Remove relationship entries which has a key entry on the other end.
// If the key entry does not have any other relationship, it is removed from Object State Manager.
// NOTE Relationship entries which have a normal entity on the other end are detached only if the relationship state is Deleted.
this.DetachRelationshipsEntries(relationshipManager);
IEntityWrapper existingWrappedEntity = _wrappedEntity;
EntityKey key = _entityKey;
EntityState state = State;
if (createKeyEntry)
{
this.DegradeEntry();
}
else
{
// If entity is in state different than Added state, entityKey should not be set to null
// EntityKey is set to null in
// ObjectStateManger.ChangeState() ->
// ObjectStateEntry.Reset() ->
// ObjectStateEntry.DetachObjectStateManagerFromEntity()
// Store data required to restore the entity key if needed.
_wrappedEntity.ObjectStateEntry = null;
_cache.ChangeState(this, this.State, EntityState.Detached);
}
// In case the detach event modifies the key.
if (state != EntityState.Added)
{
existingWrappedEntity.EntityKey = key;
}
}
//"doFixup" equals to False is called from EntityCollection & Ref code only
internal void Delete(bool doFixup)
{
ValidateState();
if (this.IsKeyEntry)
{
throw EntityUtil.CannotCallDeleteOnKeyEntry();
}
if (doFixup && this.State != EntityState.Deleted)
{
this.RelationshipManager.NullAllFKsInDependentsForWhichThisIsThePrincipal();
this.NullAllForeignKeys(); // May set conceptual nulls which will later be removed
this.FixupRelationships();
}
switch (State)
{
case EntityState.Added:
Debug.Assert(EntityState.Added == State, "Expected ObjectStateEntry state is Added; make sure FixupRelationship did not corrupt cache entry state");
_cache.ChangeState(this, EntityState.Added, EntityState.Detached);
Debug.Assert(null == _modifiedFields, "There should not be any modified fields");
break;
case EntityState.Modified:
if (!doFixup)
{
// Even when we are not doing relationship fixup at the collection level, if the entry is not a relationship
// we need to check to see if there are relationships that are referencing keys that should be removed
// this mainly occurs in cascade delete scenarios
DeleteRelationshipsThatReferenceKeys(null, null);
}
Debug.Assert(EntityState.Modified == State, "Expected ObjectStateEntry state is Modified; make sure FixupRelationship did not corrupt cache entry state");
_cache.ChangeState(this, EntityState.Modified, EntityState.Deleted);
State = EntityState.Deleted;
break;
case EntityState.Unchanged:
if (!doFixup)
{
// Even when we are not doing relationship fixup at the collection level, if the entry is not a relationship
// we need to check to see if there are relationships that are referencing keys that should be removed
// this mainly occurs in cascade delete scenarios
DeleteRelationshipsThatReferenceKeys(null, null);
}
Debug.Assert(State == EntityState.Unchanged, "Unexpected state");
Debug.Assert(EntityState.Unchanged == State, "Expected ObjectStateEntry state is Unchanged; make sure FixupRelationship did not corrupt cache entry state");
_cache.ChangeState(this, EntityState.Unchanged, EntityState.Deleted);
Debug.Assert(null == _modifiedFields, "There should not be any modified fields");
State = EntityState.Deleted;
break;
case EntityState.Deleted:
// no-op
break;
}
}
/// <summary>
/// Nulls all FK values in this entity, or sets conceptual nulls if they are not nullable.
/// </summary>
private void NullAllForeignKeys()
{
foreach (var dependent in ForeignKeyDependents)
{
EntityReference relatedEnd = WrappedEntity.RelationshipManager.GetRelatedEndInternal(
dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name) as EntityReference;
Debug.Assert(relatedEnd != null, "Expected non-null EntityReference to principal.");
relatedEnd.NullAllForeignKeys();
}
}
private bool IsOneEndOfSomeRelationship()
{
foreach (RelationshipEntry relationshipEntry in _cache.FindRelationshipsByKey(EntityKey))
{
RelationshipMultiplicity multiplicity = this.GetAssociationEndMember(relationshipEntry).RelationshipMultiplicity;
if (multiplicity == RelationshipMultiplicity.One ||
multiplicity == RelationshipMultiplicity.ZeroOrOne)
{
EntityKey targetKey = relationshipEntry.RelationshipWrapper.GetOtherEntityKey(EntityKey);
EntityEntry relatedEntry = _cache.GetEntityEntry(targetKey);
// Relationships with KeyEntries don't count.
if (!relatedEntry.IsKeyEntry)
{
return true;
}
}
}
return false;
}
// Detaches related relationship entries if other ends of these relationships are key entries.
// Detaches also related relationship entries if the entry is in Deleted state and the multiplicity is Many.
// Key entry from the other side of the relationship is removed if is not related to other entries.
private void DetachRelationshipsEntries(RelationshipManager relationshipManager)
{
Debug.Assert(relationshipManager != null, "Unexpected null RelationshipManager");
Debug.Assert(!this.IsKeyEntry, "Should only be detaching relationships with key entries if the source is not a key entry");
foreach (RelationshipEntry relationshipEntry in _cache.CopyOfRelationshipsByKey(EntityKey))
{
// Get state entry for other side of the relationship
EntityKey targetKey = relationshipEntry.RelationshipWrapper.GetOtherEntityKey(EntityKey);
Debug.Assert((object)targetKey != null, "EntityKey not on either side of relationship as expected");
EntityEntry relatedEntry = _cache.GetEntityEntry(targetKey);
if (relatedEntry.IsKeyEntry)
{
// This must be an EntityReference, so set the DetachedEntityKey if the relationship is currently Added or Unchanged
// devnote: This assumes that we are in the middle of detaching the entity associated with this state entry, because
// we don't always want to preserve the EntityKey for every detached relationship, if the source entity itself isn't being detached
if (relationshipEntry.State != EntityState.Deleted)
{
AssociationEndMember targetMember = relationshipEntry.RelationshipWrapper.GetAssociationEndMember(targetKey);
// devnote: Since we know the target end of this relationship is a key entry, it has to be a reference, so just cast
EntityReference entityReference = (EntityReference)relationshipManager.GetRelatedEndInternal(targetMember.DeclaringType.FullName, targetMember.Name);
entityReference.DetachedEntityKey = targetKey;
}
// else do nothing -- we can't null out the key for Deleted state, because there could be other relationships with this same source in a different state
// Remove key entry if necessary
relationshipEntry.DeleteUnnecessaryKeyEntries();
// Remove relationship entry
relationshipEntry.DetachRelationshipEntry();
}
else
{
// Detach deleted relationships
if (relationshipEntry.State == EntityState.Deleted)
{
RelationshipMultiplicity multiplicity = this.GetAssociationEndMember(relationshipEntry).RelationshipMultiplicity;
if (multiplicity == RelationshipMultiplicity.Many)
{
relationshipEntry.DetachRelationshipEntry();
}
}
}
}
}
private void FixupRelationships()
{
RelationshipManager relationshipManager = _wrappedEntity.RelationshipManager;
Debug.Assert(relationshipManager != null, "Entity wrapper returned a null RelationshipManager");
relationshipManager.RemoveEntityFromRelationships();
DeleteRelationshipsThatReferenceKeys(null, null);
}
/// <summary>
/// see if there are any relationship entries that point to key entries
/// if there are, remove the relationship entry
/// This is called when one of the ends of a relationship is being removed
/// </summary>
/// <param name="relationshipSet">An option relationshipSet; deletes only relationships that are part of this set</param>
internal void DeleteRelationshipsThatReferenceKeys(RelationshipSet relationshipSet, RelationshipEndMember endMember)
{
if (State != EntityState.Detached)
{
// devnote: Need to use a copy of the relationships list because we may be deleting Added
// relationships, which will be removed from the list while we are still iterating
foreach (RelationshipEntry relationshipEntry in _cache.CopyOfRelationshipsByKey(EntityKey))
{
// Only delete the relationship entry if it is not already deleted (in which case we cannot access its values)
// and when the given (optionally) relationshipSet matches the one in teh relationship entry
if ((relationshipEntry.State != EntityState.Deleted) &&
(relationshipSet == null || relationshipSet == relationshipEntry.EntitySet))
{
EntityEntry otherEnd = this.GetOtherEndOfRelationship(relationshipEntry);
if (endMember == null || endMember == otherEnd.GetAssociationEndMember(relationshipEntry))
{
for (int i = 0; i < 2; i++)
{
EntityKey entityKey = relationshipEntry.GetCurrentRelationValue(i) as EntityKey;
if ((object)entityKey != null)
{
EntityEntry relatedEntry = _cache.GetEntityEntry(entityKey);
if (relatedEntry.IsKeyEntry)
{
// remove the relationshipEntry
relationshipEntry.Delete(false);
break;
}
}
}
}
}
}
}
}
// Retrieve referential constraint properties from Principal entities (possibly recursively)
// and check referential constraint properties in the Dependent entities (1 level only)
// This code does not check the constraints on FKs because that work is instead done by
// the FK fixup code that is also called from AcceptChanges.
// Returns true if any FK relationships were skipped so that they can be checked again after fixup
private bool RetrieveAndCheckReferentialConstraintValuesInAcceptChanges()
{
RelationshipManager relationshipManager = _wrappedEntity.RelationshipManager;
Debug.Assert(relationshipManager != null, "Entity wrapper returned a null RelationshipManager");
// Find key property names which are part of referential integrity constraints
List<string> propertiesToRetrieve; // names of properties which should be retrieved from Principal entities
bool propertiesToCheckExist; // true iff there are properties which should be checked in dependent entities
// Get RI property names from metadata
bool skippedFKs = relationshipManager.FindNamesOfReferentialConstraintProperties(out propertiesToRetrieve, out propertiesToCheckExist, skipFK: true);
// Do not try to retrieve RI properties if entity doesn't participate in any RI Constraints
if (propertiesToRetrieve != null)
{
// Retrieve key values from related entities
Dictionary<string, KeyValuePair<object, IntBox>> properties;
// Create HashSet to store references to already visited entities, used to detect circular references
HashSet<object> visited = new HashSet<object>();
relationshipManager.RetrieveReferentialConstraintProperties(out properties, visited, includeOwnValues: false);
// Update properties
foreach (KeyValuePair<string, KeyValuePair<object, IntBox>> pair in properties)
{
this.SetCurrentEntityValue(pair.Key /*name*/, pair.Value.Key /*value*/);
}
}
if (propertiesToCheckExist)
{
// Compare properties of current entity with properties of the dependent entities
this.CheckReferentialConstraintPropertiesInDependents();
}
return skippedFKs;
}
internal void RetrieveReferentialConstraintPropertiesFromKeyEntries(Dictionary<string, KeyValuePair<object, IntBox>> properties)
{
string thisRole;
AssociationSet association;
// Iterate through related relationship entries
foreach (RelationshipEntry relationshipEntry in _cache.FindRelationshipsByKey(EntityKey))
{
EntityEntry otherEnd = this.GetOtherEndOfRelationship(relationshipEntry);
// We only try to retrieve properties from key entries
if (otherEnd.IsKeyEntry)
{
association = (AssociationSet)relationshipEntry.EntitySet;
Debug.Assert(association != null, "relationship is not an association");
// Iterate through referential constraints of the association of the relationship
// NOTE PERFORMANCE This collection in current stack can have 0 or 1 elements
foreach (ReferentialConstraint constraint in association.ElementType.ReferentialConstraints)
{
thisRole = this.GetAssociationEndMember(relationshipEntry).Name;
// Check if curent entry is a dependent end of the referential constraint
if (constraint.ToRole.Name == thisRole)
{
Debug.Assert(!otherEnd.EntityKey.IsTemporary, "key of key entry can't be temporary");
IList<EntityKeyMember> otherEndKeyValues = otherEnd.EntityKey.EntityKeyValues;
Debug.Assert(otherEndKeyValues != null, "key entry must have key values");
// NOTE PERFORMANCE Number of key properties is supposed to be "small"
foreach (EntityKeyMember pair in otherEndKeyValues)
{
for (int i = 0; i < constraint.FromProperties.Count; ++i)
{
if (constraint.FromProperties[i].Name == pair.Key)
{
EntityEntry.AddOrIncreaseCounter(properties, constraint.ToProperties[i].Name, pair.Value);
}
}
}
}
}
}
}
}
internal static void AddOrIncreaseCounter(Dictionary<string, KeyValuePair<object, IntBox>> properties, string propertyName, object propertyValue)
{
Debug.Assert(properties != null);
Debug.Assert(propertyName != null);
Debug.Assert(propertyValue != null);
if (properties.ContainsKey(propertyName))
{
// If this property already exists in the dictionary, check if value is the same then increase the counter
KeyValuePair<object, IntBox> valueCounterPair = properties[propertyName];
if (!ByValueEqualityComparer.Default.Equals(valueCounterPair.Key, propertyValue))
throw EntityUtil.InconsistentReferentialConstraintProperties();
else
valueCounterPair.Value.Value = valueCounterPair.Value.Value + 1;
}
else
{
// If property doesn't exist in the dictionary - add new entry with pair<value, counter>
properties[propertyName] = new KeyValuePair<object, IntBox>(propertyValue, new IntBox(1));
}
}
// Check if related dependent entities contain proper property values
// Only entities in Unchanged and Modified state are checked (including KeyEntries)
private void CheckReferentialConstraintPropertiesInDependents()
{
string thisRole;
AssociationSet association;
// Iterate through related relationship entries
foreach (RelationshipEntry relationshipEntry in _cache.FindRelationshipsByKey(EntityKey))
{
EntityEntry otherEnd = this.GetOtherEndOfRelationship(relationshipEntry);
// We only check entries which are in Unchanged or Modified state
// (including KeyEntries which are always in Unchanged State)
if (otherEnd.State == EntityState.Unchanged || otherEnd.State == EntityState.Modified)
{
association = (AssociationSet)relationshipEntry.EntitySet;
Debug.Assert(association != null, "relationship is not an association");
// Iterate through referential constraints of the association of the relationship
// NOTE PERFORMANCE This collection in current stack can have 0 or 1 elements
foreach (ReferentialConstraint constraint in association.ElementType.ReferentialConstraints)
{
thisRole = this.GetAssociationEndMember(relationshipEntry).Name;
// Check if curent entry is a principal end of the referential constraint
if (constraint.FromRole.Name == thisRole)
{
Debug.Assert(!otherEnd.EntityKey.IsTemporary, "key of Unchanged or Modified entry can't be temporary");
IList<EntityKeyMember> otherEndKeyValues = otherEnd.EntityKey.EntityKeyValues;
// NOTE PERFORMANCE Number of key properties is supposed to be "small"
foreach (EntityKeyMember pair in otherEndKeyValues)
{
for (int i = 0; i < constraint.FromProperties.Count; ++i)
{
if (constraint.ToProperties[i].Name == pair.Key)
{
if (!ByValueEqualityComparer.Default.Equals(GetCurrentEntityValue(constraint.FromProperties[i].Name), pair.Value))
{
throw EntityUtil.InconsistentReferentialConstraintProperties();
}
}
}
}
}
}
}
}
}
internal void PromoteKeyEntry(IEntityWrapper wrappedEntity, IExtendedDataRecord shadowValues, StateManagerTypeMetadata typeMetadata)
{
Debug.Assert(wrappedEntity != null, "entity wrapper cannot be null.");
Debug.Assert(wrappedEntity.Entity != null, "entity cannot be null.");
Debug.Assert(this.IsKeyEntry, "ObjectStateEntry should be a key.");
Debug.Assert(typeMetadata != null, "typeMetadata cannot be null.");
_wrappedEntity = wrappedEntity;
_wrappedEntity.ObjectStateEntry = this;
// Allow updating of cached metadata because the actual entity might be a derived type
_cacheTypeMetadata = typeMetadata;
SetChangeTrackingFlags();
#if DEBUG // performance, don't do this work in retail until shadow state is supported
if (shadowValues != null)
{
// shadowState always coms from materializer, just copy the shadowstate values
Debug.Assert(shadowValues.DataRecordInfo.RecordType.EdmType.Equals(_cacheTypeMetadata.CdmMetadata.EdmType), "different cspace metadata instance");
for (int ordinal = 0; ordinal < _cacheTypeMetadata.FieldCount; ordinal++)
{
if (_cacheTypeMetadata.IsMemberPartofShadowState(ordinal))
{
Debug.Assert(false, "shadowstate not supported");
}
}
}
#endif
}
/// <summary>
/// Turns this entry into a key entry (SPAN stub).
/// </summary>
internal void DegradeEntry()
{
Debug.Assert(!this.IsKeyEntry);
Debug.Assert((object)_entityKey != null);
_entityKey = EntityKey; //Performs validation.
RemoveFromForeignKeyIndex();
_wrappedEntity.SetChangeTracker(null);
_modifiedFields = null;
_originalValues = null;
_originalComplexObjects = null;
// we don't want temporary keys to exist outside of the context
if (State == EntityState.Added)
{
_wrappedEntity.EntityKey = null;
_entityKey = null;
}
if (State != EntityState.Unchanged)
{
_cache.ChangeState(this, this.State, EntityState.Unchanged);
State = EntityState.Unchanged;
}
_cache.RemoveEntryFromKeylessStore(_wrappedEntity);
_wrappedEntity.DetachContext();
_wrappedEntity.ObjectStateEntry = null;
object degradedEntity = _wrappedEntity.Entity;
_wrappedEntity = EntityWrapperFactory.NullWrapper;
SetChangeTrackingFlags();
_cache.OnObjectStateManagerChanged(CollectionChangeAction.Remove, degradedEntity);
Debug.Assert(this.IsKeyEntry);
}
internal void AttachObjectStateManagerToEntity()
{
// This method should only be called in cases where we really have an entity to attach to
Debug.Assert(_wrappedEntity.Entity != null, "Cannot attach a null entity to the state manager");
_wrappedEntity.SetChangeTracker(this);
_wrappedEntity.TakeSnapshot(this);
}
// Get values of key properties which doesn't already exist in passed in 'properties'
internal void GetOtherKeyProperties(Dictionary<string, KeyValuePair<object, IntBox>> properties)
{
Debug.Assert(properties != null);
Debug.Assert(_cacheTypeMetadata != null);
Debug.Assert(_cacheTypeMetadata.DataRecordInfo != null);
Debug.Assert(_cacheTypeMetadata.DataRecordInfo.RecordType != null);
EntityType entityType = _cacheTypeMetadata.DataRecordInfo.RecordType.EdmType as EntityType;
Debug.Assert(entityType != null, "EntityType == null");
foreach (EdmMember member in entityType.KeyMembers)
{
if (!properties.ContainsKey(member.Name))
{
properties[member.Name] = new KeyValuePair<object, IntBox>(this.GetCurrentEntityValue(member.Name), new IntBox(1));
}
}
}
internal void AddOriginalValue(StateManagerMemberMetadata memberMetadata, object userObject, object value)
{
if (null == _originalValues)
{
_originalValues = new List<StateManagerValue>();
}
_originalValues.Add(new StateManagerValue(memberMetadata, userObject, value));
}
internal void CompareKeyProperties(object changed)
{
Debug.Assert(changed != null, "changed object is null");
Debug.Assert(!this.IsKeyEntry);
StateManagerTypeMetadata metadata = this._cacheTypeMetadata;
int fieldCount = this.GetFieldCount(metadata);
object currentValueNew;
object currentValueOld;
for (int i = 0; i < fieldCount; i++)
{
StateManagerMemberMetadata member = metadata.Member(i);
if (member.IsPartOfKey)
{
Debug.Assert(!member.IsComplex);
currentValueNew = member.GetValue(changed);
currentValueOld = member.GetValue(_wrappedEntity.Entity);
if (!ByValueEqualityComparer.Default.Equals(currentValueNew, currentValueOld))
{
throw EntityUtil.CannotModifyKeyProperty(member.CLayerName);
}
}
}
}
// helper method used to get value of property
internal object GetCurrentEntityValue(string memberName)
{
int ordinal = _cacheTypeMetadata.GetOrdinalforOLayerMemberName(memberName);
return GetCurrentEntityValue(_cacheTypeMetadata, ordinal, _wrappedEntity.Entity, ObjectStateValueRecord.CurrentUpdatable);
}
/// <summary>
/// Verifies that the property with the given ordinal is editable.
/// </summary>
/// <exception cref="InvalidOperationException">the property is not editable</exception>
internal void VerifyEntityValueIsEditable(StateManagerTypeMetadata typeMetadata, int ordinal, string memberName)
{
if (this.State == EntityState.Deleted)
{
throw EntityUtil.CantModifyDetachedDeletedEntries();
}
Debug.Assert(typeMetadata != null, "Cannot verify entity or complex object is editable if typeMetadata is null.");
StateManagerMemberMetadata member = typeMetadata.Member(ordinal);
Debug.Assert(member != null, "Member shouldn't be null.");
// Key fields are only editable if the entry is the Added state.
if (member.IsPartOfKey && State != EntityState.Added)
{
throw EntityUtil.CannotModifyKeyProperty(memberName);
}
}
// This API are mainly for DbDataRecord implementations to get and set the values
// also for loadoptions, setoldvalue will be used.
// we should handle just for C-space, we will not recieve a call from O-space for set
// We will not also return any value in term of O-Layer. all set and gets for us is in terms of C-layer.
// the only O-layer interaction we have is through delegates from entity.
internal void SetCurrentEntityValue(StateManagerTypeMetadata metadata, int ordinal, object userObject, object newValue)
{
// required to validate state because entity could be detatched from this context and added to another context
// and we want this to fail instead of setting the value which would redirect to the other context
ValidateState();
StateManagerMemberMetadata member = metadata.Member(ordinal);
Debug.Assert(member != null, "StateManagerMemberMetadata was not found for the given ordinal.");
if (member.IsComplex)
{
if (newValue == null || newValue == DBNull.Value)
{
throw EntityUtil.NullableComplexTypesNotSupported(member.CLayerName);
}
IExtendedDataRecord newValueRecord = newValue as IExtendedDataRecord;
if (newValueRecord == null)
{
throw EntityUtil.InvalidTypeForComplexTypeProperty("value");
}
newValue = _cache.ComplexTypeMaterializer.CreateComplex(newValueRecord, newValueRecord.DataRecordInfo, null);
}
_wrappedEntity.SetCurrentValue(this, member, ordinal, userObject, newValue);
}
private void TransitionRelationshipsForAdd()
{
foreach (RelationshipEntry relationshipEntry in _cache.CopyOfRelationshipsByKey(this.EntityKey))
{
// Unchanged -> Added
if (relationshipEntry.State == EntityState.Unchanged)
{
this.ObjectStateManager.ChangeState(relationshipEntry, EntityState.Unchanged, EntityState.Added);
relationshipEntry.State = EntityState.Added;
}
// Deleted -> Detached
else if (relationshipEntry.State == EntityState.Deleted)
{
// Remove key entry if necessary
relationshipEntry.DeleteUnnecessaryKeyEntries();
// Remove relationship entry
relationshipEntry.DetachRelationshipEntry();
}
}
}
[Conditional("DEBUG")]
private void VerifyIsNotRelated()
{
Debug.Assert(!this.IsKeyEntry, "shouldn't be called for a key entry");
this.WrappedEntity.RelationshipManager.VerifyIsNotRelated();
}
internal void ChangeObjectState(EntityState requestedState)
{
if (this.IsKeyEntry)
{
if (requestedState == EntityState.Unchanged)
{
return; // No-op
}
throw EntityUtil.CannotModifyKeyEntryState();
}
switch (this.State)
{
case EntityState.Added:
switch (requestedState)
{
case EntityState.Added:
// Relationship fixup: Unchanged -> Added, Deleted -> Detached
this.TransitionRelationshipsForAdd();
break;
case EntityState.Unchanged:
// Relationship fixup: none
this.AcceptChanges();
break;
case EntityState.Modified:
// Relationship fixup: none
this.AcceptChanges();
this.SetModified();
this.SetModifiedAll();
break;
case EntityState.Deleted:
// Need to forget conceptual nulls so that AcceptChanges does not throw.
// Note that there should always be no conceptual nulls left when we get into the Deleted state.
_cache.ForgetEntryWithConceptualNull(this, resetAllKeys: true);
// Relationship fixup: Added -> Detached, Unchanged -> Deleted
this.AcceptChanges();
// NOTE: OSM.TransactionManager.IsLocalPublicAPI == true so cascade delete and RIC are disabled
this.Delete(true);
break;
case EntityState.Detached:
// Relationship fixup: * -> Detached
this.Detach();
break;
default:
throw EntityUtil.InvalidEntityStateArgument("state");
}
break;
case EntityState.Unchanged:
switch (requestedState)
{
case EntityState.Added:
this.ObjectStateManager.ReplaceKeyWithTemporaryKey(this);
this._modifiedFields = null;
this._originalValues = null;
this._originalComplexObjects = null;
this.State = EntityState.Added;
// Relationship fixup: Unchanged -> Added, Deleted -> Detached
this.TransitionRelationshipsForAdd();
break;
case EntityState.Unchanged:
// Relationship fixup: none
break;
case EntityState.Modified:
// Relationship fixup: none
this.SetModified();
this.SetModifiedAll();
break;
case EntityState.Deleted:
// Relationship fixup: Added -> Detached, Unchanged -> Deleted
// NOTE: OSM.TransactionManager.IsLocalPublicAPI == true so cascade delete and RIC are disabled
this.Delete(true);
break;
case EntityState.Detached:
// Relationship fixup: * -> Detached
this.Detach();
break;
default:
throw EntityUtil.InvalidEntityStateArgument("state");
}
break;
case EntityState.Modified:
switch (requestedState)
{
case EntityState.Added:
this.ObjectStateManager.ReplaceKeyWithTemporaryKey(this);
this._modifiedFields = null;
this._originalValues = null;
this._originalComplexObjects = null;
this.State = EntityState.Added;
// Relationship fixup: Unchanged -> Added, Deleted -> Detached
this.TransitionRelationshipsForAdd();
break;
case EntityState.Unchanged:
this.AcceptChanges();
// Relationship fixup: none
break;
case EntityState.Modified:
// Relationship fixup: none
this.SetModified();
this.SetModifiedAll();
break;
case EntityState.Deleted:
// Relationship fixup: Added -> Detached, Unchanged -> Deleted
// NOTE: OSM.TransactionManager.IsLocalPublicAPI == true so cascade delete and RIC are disabled
this.Delete(true);
break;
case EntityState.Detached:
// Relationship fixup: * -> Detached
this.Detach();
break;
default:
throw EntityUtil.InvalidEntityStateArgument("state");
}
break;
case EntityState.Deleted:
switch (requestedState)
{
case EntityState.Added:
// Throw if the entry has some not-Deleted relationships
this.VerifyIsNotRelated();
this.TransitionRelationshipsForAdd();
this.ObjectStateManager.ReplaceKeyWithTemporaryKey(this);
this._modifiedFields = null;
this._originalValues = null;
this._originalComplexObjects = null;
this.State = EntityState.Added;
_cache.FixupReferencesByForeignKeys(this); // Make sure refs based on FK values are set
_cache.OnObjectStateManagerChanged(CollectionChangeAction.Add, Entity);
break;
case EntityState.Unchanged:
// Throw if the entry has some not-Deleted relationship
this.VerifyIsNotRelated();
this._modifiedFields = null;
this._originalValues = null;
this._originalComplexObjects = null;
this.ObjectStateManager.ChangeState(this, EntityState.Deleted, EntityState.Unchanged);
this.State = EntityState.Unchanged;
_wrappedEntity.TakeSnapshot(this); // refresh snapshot
_cache.FixupReferencesByForeignKeys(this); // Make sure refs based on FK values are set
_cache.OnObjectStateManagerChanged(CollectionChangeAction.Add, Entity);
// Relationship fixup: none
break;
case EntityState.Modified:
// Throw if the entry has some not-Deleted relationship
this.VerifyIsNotRelated();
// Relationship fixup: none
this.ObjectStateManager.ChangeState(this, EntityState.Deleted, EntityState.Modified);
this.State = EntityState.Modified;
this.SetModifiedAll();
_cache.FixupReferencesByForeignKeys(this); // Make sure refs based on FK values are set
_cache.OnObjectStateManagerChanged(CollectionChangeAction.Add, Entity);
break;
case EntityState.Deleted:
// No-op
break;
case EntityState.Detached:
// Relationship fixup: * -> Detached
this.Detach();
break;
default:
throw EntityUtil.InvalidEntityStateArgument("state");
}
break;
case EntityState.Detached:
Debug.Fail("detached entry");
break;
}
}
internal void UpdateOriginalValues(object entity)
{
Debug.Assert(EntityState.Added != this.State, "Cannot change original values of an entity in the Added state");
EntityState oldState = this.State;
this.UpdateRecordWithSetModified(entity, this.EditableOriginalValues);
if (oldState == EntityState.Unchanged && this.State == EntityState.Modified)
{
// The UpdateRecord changes state but doesn't update ObjectStateManager's dictionaries.
this.ObjectStateManager.ChangeState(this, oldState, EntityState.Modified);
}
}
internal void UpdateRecordWithoutSetModified(object value, DbUpdatableDataRecord current)
{
UpdateRecord(value, current, UpdateRecordBehavior.WithoutSetModified, s_EntityRoot);
}
internal void UpdateRecordWithSetModified(object value, DbUpdatableDataRecord current)
{
UpdateRecord(value, current, UpdateRecordBehavior.WithSetModified, s_EntityRoot);
}
private enum UpdateRecordBehavior
{
WithoutSetModified,
WithSetModified
}
internal const int s_EntityRoot = -1;
private void UpdateRecord(object value, DbUpdatableDataRecord current, UpdateRecordBehavior behavior, int propertyIndex)
{
Debug.Assert(null != value, "null value");
Debug.Assert(null != current, "null CurrentValueRecord");
Debug.Assert(!(value is IEntityWrapper));
Debug.Assert(propertyIndex == s_EntityRoot ||
propertyIndex >= 0, "Unexpected index. Use -1 if the passed value is an entity, not a complex type object");
// get Metadata for type
StateManagerTypeMetadata typeMetadata = current._metadata;
DataRecordInfo recordInfo = typeMetadata.DataRecordInfo;
foreach (FieldMetadata field in recordInfo.FieldMetadata)
{
int index = field.Ordinal;
var member = typeMetadata.Member(index);
object fieldValue = member.GetValue(value) ?? DBNull.Value;
if (Helper.IsComplexType(field.FieldType.TypeUsage.EdmType))
{
object existing = current.GetValue(index);
// Ensure that the existing ComplexType value is not null. This is not supported.
if (existing == DBNull.Value)
{
throw EntityUtil.NullableComplexTypesNotSupported(field.FieldType.Name);
}
else if (fieldValue != DBNull.Value)
{
// There is both an IExtendedDataRecord and an existing CurrentValueRecord
// This part is different than Shaper.UpdateRecord - we have to remember the name of property on the entity (for complex types)
// For property of a complex type the rootCLayerName is CLayerName of the complex property on the entity.
this.UpdateRecord(fieldValue, (DbUpdatableDataRecord)existing,
behavior,
propertyIndex == s_EntityRoot ? index : propertyIndex);
}
}
else
{
Debug.Assert(Helper.IsScalarType(field.FieldType.TypeUsage.EdmType), "Expected primitive or enum type.");
// Set the new value if it doesn't match the existing value or if the field is modified, not a primary key, and
// this entity has a conceptual null, since setting the field may then clear the conceptual null--see 640443.
if (HasRecordValueChanged(current, index, fieldValue) && !member.IsPartOfKey)
{
current.SetValue(index, fieldValue);
if (behavior == UpdateRecordBehavior.WithSetModified)
{
// This part is different than Shaper.UpdateRecord - we have to mark the field as modified.
// For property of a complex type the rootCLayerName is CLayerName of the complex property on the entity.
SetModifiedPropertyInternal(propertyIndex == s_EntityRoot ? index : propertyIndex);
}
}
}
}
}
internal bool HasRecordValueChanged(DbDataRecord record, int propertyIndex, object newFieldValue)
{
object existing = record.GetValue(propertyIndex);
return (existing != newFieldValue) &&
(((object)DBNull.Value == newFieldValue) ||
((object)DBNull.Value == existing) ||
(!ByValueEqualityComparer.Default.Equals(existing, newFieldValue))) ||
(_cache.EntryHasConceptualNull(this) && _modifiedFields != null && _modifiedFields[propertyIndex]);
}
internal void ApplyCurrentValuesInternal(IEntityWrapper wrappedCurrentEntity)
{
Debug.Assert(!IsKeyEntry, "Cannot apply values to a key KeyEntry.");
Debug.Assert(wrappedCurrentEntity != null, "null entity wrapper");
if (this.State != EntityState.Modified &&
this.State != EntityState.Unchanged)
{
throw EntityUtil.EntityMustBeUnchangedOrModified(this.State);
}
if (this.WrappedEntity.IdentityType != wrappedCurrentEntity.IdentityType)
{
throw EntityUtil.EntitiesHaveDifferentType(this.Entity.GetType().FullName, wrappedCurrentEntity.Entity.GetType().FullName);
}
this.CompareKeyProperties(wrappedCurrentEntity.Entity);
UpdateCurrentValueRecord(wrappedCurrentEntity.Entity);
}
internal void UpdateCurrentValueRecord(object value)
{
Debug.Assert(!(value is IEntityWrapper));
_wrappedEntity.UpdateCurrentValueRecord(value, this);
}
internal void ApplyOriginalValuesInternal(IEntityWrapper wrappedOriginalEntity)
{
Debug.Assert(!IsKeyEntry, "Cannot apply values to a key KeyEntry.");
Debug.Assert(wrappedOriginalEntity != null, "null entity wrapper");
if (this.State != EntityState.Modified &&
this.State != EntityState.Unchanged &&
this.State != EntityState.Deleted)
{
throw EntityUtil.EntityMustBeUnchangedOrModifiedOrDeleted(this.State);
}
if (this.WrappedEntity.IdentityType != wrappedOriginalEntity.IdentityType)
{
throw EntityUtil.EntitiesHaveDifferentType(this.Entity.GetType().FullName, wrappedOriginalEntity.Entity.GetType().FullName);
}
this.CompareKeyProperties(wrappedOriginalEntity.Entity);
// The ObjectStateEntry.UpdateModifiedFields uses a variation of Shaper.UpdateRecord method
// which additionaly marks properties as modified as necessary.
this.UpdateOriginalValues(wrappedOriginalEntity.Entity);
}
/// <summary>
/// For each FK contained in this entry, the entry is removed from the index maintained by
/// the ObjectStateManager for that key.
/// </summary>
internal void RemoveFromForeignKeyIndex()
{
if (!this.IsKeyEntry)
{
foreach (EntityReference relatedEnd in FindFKRelatedEnds())
{
foreach(EntityKey foreignKey in relatedEnd.GetAllKeyValues())
{
_cache.RemoveEntryFromForeignKeyIndex(foreignKey, this);
}
}
_cache.AssertEntryDoesNotExistInForeignKeyIndex(this);
}
}
/// <summary>
/// Looks at the foreign keys contained in this entry and performs fixup to the entities that
/// they reference, or adds the key and this entry to the index of foreign keys that reference
/// entities that we don't yet know about.
/// </summary>
internal void FixupReferencesByForeignKeys(bool replaceAddedRefs)
{
Debug.Assert(_cache != null, "Attempt to fixup detached entity entry");
_cache.TransactionManager.BeginGraphUpdate();
bool setIsLoaded = !(_cache.TransactionManager.IsAttachTracking || _cache.TransactionManager.IsAddTracking);
try
{
foreach (var dependent in ForeignKeyDependents)
{
EntityReference relatedEnd = WrappedEntity.RelationshipManager.GetRelatedEndInternal(
dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name) as EntityReference;
Debug.Assert(relatedEnd != null, "Expected non-null EntityReference to principal.");
// Prevent fixup using values that are effectivly null but aren't nullable.
if (!ForeignKeyFactory.IsConceptualNullKey(relatedEnd.CachedForeignKey))
{
FixupEntityReferenceToPrincipal(relatedEnd, null, setIsLoaded, replaceAddedRefs);
}
}
}
finally
{
_cache.TransactionManager.EndGraphUpdate();
}
}
internal void FixupEntityReferenceByForeignKey(EntityReference reference)
{
// The FK is changing, so the reference is no longer loaded from the store, even if we do fixup
reference.SetIsLoaded(false);
// Remove the existing CachedForeignKey
bool hasConceptualNullFk = ForeignKeyFactory.IsConceptualNullKey(reference.CachedForeignKey);
if (hasConceptualNullFk)
{
ObjectStateManager.ForgetEntryWithConceptualNull(this, resetAllKeys: false);
}
IEntityWrapper existingPrincipal = reference.ReferenceValue;
EntityKey foreignKey = ForeignKeyFactory.CreateKeyFromForeignKeyValues(this, reference);
// Check if the new FK matches the key of the entity already at the principal end.
// If it does, then don't change the ref.
bool needToSetRef;
if ((object)foreignKey == null || existingPrincipal.Entity == null)
{
needToSetRef = true;
}
else
{
EntityKey existingPrincipalKey = existingPrincipal.EntityKey;
EntityEntry existingPrincipalEntry = existingPrincipal.ObjectStateEntry;
// existingPrincipalKey may be null if this fixup code is being called in the middle of
// adding an object. This can happen when using change tracking proxies with fixup.
if ((existingPrincipalKey == null || existingPrincipalKey.IsTemporary) && existingPrincipalEntry != null)
{
// Build a temporary non-temp key for the added entity so we can see if it matches the new FK
existingPrincipalKey = new EntityKey((EntitySet)existingPrincipalEntry.EntitySet, (IExtendedDataRecord)existingPrincipalEntry.CurrentValues);
}
// If existingPrincipalKey is still a temp key here, then the equality check will fail
needToSetRef = !foreignKey.Equals(existingPrincipalKey);
}
if (_cache.TransactionManager.RelationshipBeingUpdated != reference)
{
if (needToSetRef)
{
ObjectStateManager stateManager = _cache;
_cache.TransactionManager.BeginGraphUpdate();
// Keep track of this entity so that we don't try to delete/detach the entity while we're
// working with it. This allows the FK to be set to some value without that entity being detached.
// However, if the FK is being set to null, then for an identifying relationship we will detach.
if ((object)foreignKey != null)
{
_cache.TransactionManager.EntityBeingReparented = Entity;
}
try
{
FixupEntityReferenceToPrincipal(reference, foreignKey, setIsLoaded: false, replaceExistingRef: true);
}
finally
{
Debug.Assert(_cache != null, "Unexpected null state manager.");
_cache.TransactionManager.EntityBeingReparented = null;
_cache.TransactionManager.EndGraphUpdate();
}
}
}
else
{
// We only want to update the CachedForeignKey and not touch the EntityReference.Value/EntityKey
FixupEntityReferenceToPrincipal(reference, foreignKey, setIsLoaded: false, replaceExistingRef: false);
}
}
/// <summary>
/// Given a RelatedEnd that represents a FK from this dependent entity to the principal entity of the
/// relationship, this method fixes up references between the two entities.
/// </summary>
/// <param name="relatedEnd">Represents a FK relationship to a principal</param>
/// <param name="foreignKey">The foreign key, if it has already been computed</param>
/// <param name="setIsLoaded">If true, then the IsLoaded flag for the relationship is set</param>
/// <param name="replaceExistingRef">If true, then any existing references will be replaced</param>
internal void FixupEntityReferenceToPrincipal(EntityReference relatedEnd, EntityKey foreignKey, bool setIsLoaded, bool replaceExistingRef)
{
Debug.Assert(relatedEnd != null, "Found null RelatedEnd or EntityCollection to principal");
if (foreignKey == null)
{
foreignKey = ForeignKeyFactory.CreateKeyFromForeignKeyValues(this, relatedEnd);
}
// Note that if we're not changing FKs directly, but rather as a result of fixup after a ref has changed,
// and if the entity currently being pointed to is Added, then we shouldn't clobber it, because a ref to
// an Added entity wins in this case.
bool canModifyReference = _cache.TransactionManager.RelationshipBeingUpdated != relatedEnd &&
(!_cache.TransactionManager.IsForeignKeyUpdate ||
relatedEnd.ReferenceValue.ObjectStateEntry == null ||
relatedEnd.ReferenceValue.ObjectStateEntry.State != EntityState.Added);
//
relatedEnd.SetCachedForeignKey(foreignKey, this);
ObjectStateManager.ForgetEntryWithConceptualNull(this, resetAllKeys: false);
if (foreignKey != null) // Implies no value is null or CreateKeyFromForeignKeyValues would have returned null
{
// Lookup key in OSM. If found, then we can do fixup. If not, then need to add to index
// Should not overwrite a reference at this point since this might cause the graph to
// be shredded. This allows us to correctly detect key violations or RIC violations later.
EntityEntry principalEntry;
if (_cache.TryGetEntityEntry(foreignKey, out principalEntry) &&
!principalEntry.IsKeyEntry &&
principalEntry.State != EntityState.Deleted &&
(replaceExistingRef || WillNotRefSteal(relatedEnd, principalEntry.WrappedEntity)) &&
relatedEnd.CanSetEntityType(principalEntry.WrappedEntity))
{
if (canModifyReference)
{
// We add both sides to the promoted EntityKeyRefs collection because it could be the dependent or
// the principal or both that are being added. Having extra members in this index doesn't hurt.
if (_cache.TransactionManager.PopulatedEntityReferences != null)
{
Debug.Assert(_cache.TransactionManager.IsAddTracking || _cache.TransactionManager.IsAttachTracking,
"PromotedEntityKeyRefs is non-null while not tracking add or attach");
_cache.TransactionManager.PopulatedEntityReferences.Add(relatedEnd);
}
// Set the EntityKey on the RelatedEnd--this will cause the reference to be set and fixup to happen.
relatedEnd.SetEntityKey(foreignKey, forceFixup: true);
if (_cache.TransactionManager.PopulatedEntityReferences != null)
{
EntityReference otherEnd = relatedEnd.GetOtherEndOfRelationship(principalEntry.WrappedEntity) as EntityReference;
if (otherEnd != null)
{
_cache.TransactionManager.PopulatedEntityReferences.Add(otherEnd);
}
}
}
if (setIsLoaded && principalEntry.State != EntityState.Added)
{
relatedEnd.SetIsLoaded(true);
}
}
else
{
// Add an entry to the index for later fixup
_cache.AddEntryContainingForeignKeyToIndex(foreignKey, this);
if (canModifyReference && replaceExistingRef && relatedEnd.ReferenceValue.Entity != null)
{
relatedEnd.ReferenceValue = EntityWrapperFactory.NullWrapper;
}
}
}
else if(canModifyReference)
{
if (replaceExistingRef && (relatedEnd.ReferenceValue.Entity != null || relatedEnd.EntityKey != null))
{
relatedEnd.ReferenceValue = EntityWrapperFactory.NullWrapper;
}
if (setIsLoaded)
{
// This is the case where a query comes from the database with a null FK value.
// We know that there is no related entity in the database and therefore the entity on the
// other end of the relationship is as loaded as it is possible to be. Therefore, we
// set the IsLoaded flag so that if a user asks we will tell them that (based on last known
// state of the database) there is no need to do a load.
relatedEnd.SetIsLoaded(true);
}
}
}
/// <summary>
/// Determins whether or not setting a reference will cause implicit ref stealing as part of FK fixup.
/// If it would, then an exception is thrown. If it would not and we can safely overwrite the existing
/// value, then true is returned. If it would not but we should not overwrite the existing value,
/// then false is returned.
private bool WillNotRefSteal(EntityReference refToPrincipal, IEntityWrapper wrappedPrincipal)
{
RelatedEnd dependentEnd = refToPrincipal.GetOtherEndOfRelationship(wrappedPrincipal);
EntityReference refToDependent = dependentEnd as EntityReference;
if ((refToPrincipal.ReferenceValue.Entity == null && refToPrincipal.NavigationPropertyIsNullOrMissing()) &&
(refToDependent == null || (refToDependent.ReferenceValue.Entity == null && refToDependent.NavigationPropertyIsNullOrMissing())))
{
// Return true if the ref to principal is null and it's not 1:1 or it is 1:1 and the ref to dependent is also null.
return true;
}
else if (refToDependent != null &&
(Object.ReferenceEquals(refToDependent.ReferenceValue.Entity, refToPrincipal.WrappedOwner.Entity) ||
refToDependent.CheckIfNavigationPropertyContainsEntity(refToPrincipal.WrappedOwner)))
{
return true;
}
else if (refToDependent == null ||
Object.ReferenceEquals(refToPrincipal.ReferenceValue.Entity, wrappedPrincipal.Entity) ||
refToPrincipal.CheckIfNavigationPropertyContainsEntity(wrappedPrincipal))
{
// Return false if the ref to principal is non-null and it's not 1:1
return false;
}
else
{
// Else it is 1:1 and one side or the other is non-null => reference steal!
throw EntityUtil.CannotAddMoreThanOneEntityToEntityReference(
refToDependent.RelationshipNavigation.To,
refToDependent.RelationshipNavigation.RelationshipName);
}
}
/// <summary>
/// Given that this entry represents an entity on the dependent side of a FK, this method attempts to return the key of the
/// entity on the principal side of the FK. If the two entities both exist in the context, then the primary key of
/// the principal entity is found and returned. If the principal entity does not exist in the context, then a key
/// for it is built up from the foreign key values contained in the dependent entity.
/// </summary>
/// <param name="principalRole">The role indicating the FK to navigate</param>
/// <param name="principalKey">Set to the principal key or null on return</param>
/// <returns>True if the principal key was found or built; false if it could not be found or built</returns>
internal bool TryGetReferenceKey(AssociationEndMember principalRole, out EntityKey principalKey)
{
EntityReference relatedEnd = (RelatedEnd)RelationshipManager.GetRelatedEnd(principalRole.DeclaringType.FullName, principalRole.Name) as EntityReference;
Debug.Assert(relatedEnd != null, "Expected there to be a non null EntityReference to the principal");
if (relatedEnd.CachedValue.Entity == null || relatedEnd.CachedValue.ObjectStateEntry == null)
{
principalKey = null;
return false;
}
//
principalKey = relatedEnd.EntityKey ?? relatedEnd.CachedValue.ObjectStateEntry.EntityKey;
return principalKey != null;
}
/// <summary>
/// Performs fixuyup of foreign keys based on referencesd between objects. This should only be called
/// for Added objects since this is the only time that references take precedence over FKs in fixup.
/// </summary>
internal void FixupForeignKeysByReference()
{
Debug.Assert(_cache != null, "Attempt to fixup detached entity entry");
_cache.TransactionManager.BeginFixupKeysByReference();
try
{
FixupForeignKeysByReference(null);
}
finally
{
_cache.TransactionManager.EndFixupKeysByReference();
}
}
/// <summary>
/// Fixup the FKs by the current reference values
/// Do this in the order of fixing up values from the principal ends first, and then propogate those values to the dependents
/// </summary>
/// <param name="visited"></param>
private void FixupForeignKeysByReference(List<EntityEntry> visited)
{
EntitySet entitySet = EntitySet as EntitySet;
// Perf optimization to avoid all this work if the entity doesn't participate in any FK relationships
if (!entitySet.HasForeignKeyRelationships)
{
return;
}
foreach (var dependent in ForeignKeyDependents)
{
// Added dependent. Make sure we traverse all the way to the top-most principal before beginging fixup.
EntityReference reference = RelationshipManager.GetRelatedEndInternal(dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name) as EntityReference;
Debug.Assert(reference != null, "Expected reference to exist and be an entity reference (not collection)");
IEntityWrapper existingPrincipal = reference.ReferenceValue;
if (existingPrincipal.Entity != null)
{
EntityEntry principalEntry = existingPrincipal.ObjectStateEntry;
bool? isOneToMany = null;
if (principalEntry != null && principalEntry.State == EntityState.Added &&
(principalEntry != this || (isOneToMany = reference.GetOtherEndOfRelationship(existingPrincipal) is EntityReference).Value))
{
visited = visited ?? new List<EntityEntry>();
if (visited.Contains(this))
{
if (!isOneToMany.HasValue)
{
isOneToMany = reference.GetOtherEndOfRelationship(existingPrincipal) is EntityReference;
}
if (isOneToMany.Value)
{
// Cycles in constraints are dissallowed except for 1:* self references
throw EntityUtil.CircularRelationshipsWithReferentialConstraints();
}
}
else
{
visited.Add(this);
principalEntry.FixupForeignKeysByReference(visited);
visited.Remove(this);
}
}
// "forceChange" is false because we don't want to actually set the property values
// here if they are aready set to the same thing--we don't want the events and setting
// the modified flag is irrelavent during AcceptChanges.
reference.UpdateForeignKeyValues(this.WrappedEntity, existingPrincipal, changedFKs: null, forceChange: false);
}
else
{
EntityKey principalKey = reference.EntityKey;
if (principalKey != null && !principalKey.IsTemporary)
{
reference.UpdateForeignKeyValues(this.WrappedEntity, principalKey);
}
}
}
foreach (var principal in ForeignKeyPrincipals)
{
// Added prinipal end. Fixup FKs on all dependents.
// This is necessary because of the case where a PK in an added entity is changed after it and its dependnents
// are added to the context--see bug 628752.
bool fkOverlapsPk = false; // Set to true if we find out that the FK overlaps the dependent PK
bool dependentPropsChecked = false; // Set to true once we have checked whether or not the FK overlaps the PK
EntityKey principalKey = WrappedEntity.EntityKey;
RelatedEnd principalEnd = RelationshipManager.GetRelatedEndInternal(principal.Item1.ElementType.FullName, principal.Item2.ToRole.Name);
foreach (IEntityWrapper dependent in principalEnd.GetWrappedEntities())
{
EntityEntry dependentEntry = dependent.ObjectStateEntry;
Debug.Assert(dependentEntry != null, "Should have fully tracked graph at this point.");
if (dependentEntry.State != EntityState.Added && !dependentPropsChecked)
{
dependentPropsChecked = true;
foreach (EdmProperty dependentProp in principal.Item2.ToProperties)
{
int dependentOrdinal = dependentEntry._cacheTypeMetadata.GetOrdinalforOLayerMemberName(dependentProp.Name);
StateManagerMemberMetadata member = dependentEntry._cacheTypeMetadata.Member(dependentOrdinal);
if (member.IsPartOfKey)
{
// If the FK overlpas the PK then we can't set it for non-Added entities.
// In this situation we just continue with the next one and if the conflict
// may then be flagged later as a RIC check.
fkOverlapsPk = true;
break;
}
}
}
// This code relies on the fact that a dependent referenced to an Added principal must be either Added or
// Modified since we cannpt trust thestate of the principal PK and therefore the dependent FK must also
// be considered not completely trusted--it may need to be updated.
if (dependentEntry.State == EntityState.Added || (dependentEntry.State == EntityState.Modified && !fkOverlapsPk))
{
EntityReference principalRef = principalEnd.GetOtherEndOfRelationship(dependent) as EntityReference;
Debug.Assert(principalRef != null, "Expected reference to exist and be an entity reference (not collection)");
// "forceChange" is false because we don't want to actually set the property values
// here if they are aready set to the same thing--we don't want the events and setting
// the modified flag is irrelavent during AcceptChanges.
principalRef.UpdateForeignKeyValues(dependent, WrappedEntity, changedFKs: null, forceChange: false);
}
}
}
}
private bool IsPropertyAForeignKey(string propertyName)
{
foreach (var dependent in ForeignKeyDependents)
{
foreach (EdmProperty property in dependent.Item2.ToProperties)
{
if (property.Name == propertyName)
{
return true;
}
}
}
return false;
}
private bool IsPropertyAForeignKey(string propertyName, out List<Pair<string, string>> relationships)
{
relationships = null;
foreach (var dependent in ForeignKeyDependents)
{
foreach (EdmProperty property in dependent.Item2.ToProperties)
{
if (property.Name == propertyName)
{
if (relationships == null)
{
relationships = new List<Pair<string, string>>();
}
relationships.Add(new Pair<string, string>(dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name));
break;
}
}
}
return relationships != null;
}
internal void FindRelatedEntityKeysByForeignKeys(
out Dictionary<RelatedEnd, HashSet<EntityKey>> relatedEntities,
bool useOriginalValues)
{
relatedEntities = null;
foreach (var dependent in ForeignKeyDependents)
{
AssociationSet associationSet = dependent.Item1;
ReferentialConstraint constraint = dependent.Item2;
// Get association end members for the dependent and the principal ends
string dependentId = constraint.ToRole.Identity;
var setEnds = associationSet.AssociationSetEnds;
Debug.Assert(associationSet.AssociationSetEnds.Count == 2, "Expected an association set with only two ends.");
AssociationEndMember dependentEnd;
AssociationEndMember principalEnd;
if (setEnds[0].CorrespondingAssociationEndMember.Identity == dependentId)
{
dependentEnd = setEnds[0].CorrespondingAssociationEndMember;
principalEnd = setEnds[1].CorrespondingAssociationEndMember;
}
else
{
dependentEnd = setEnds[1].CorrespondingAssociationEndMember;
principalEnd = setEnds[0].CorrespondingAssociationEndMember;
}
EntitySet principalEntitySet = MetadataHelper.GetEntitySetAtEnd(associationSet, principalEnd);
EntityKey foreignKey = ForeignKeyFactory.CreateKeyFromForeignKeyValues(this, constraint, principalEntitySet, useOriginalValues);
if (foreignKey != null) // Implies no value is null or CreateKeyFromForeignKeyValues would have returned null
{
EntityReference reference = RelationshipManager.GetRelatedEndInternal(
associationSet.ElementType.FullName, constraint.FromRole.Name) as EntityReference;
// only for deleted relationships the hashset can have > 1 elements
HashSet<EntityKey> entityKeys;
relatedEntities = relatedEntities != null ? relatedEntities : new Dictionary<RelatedEnd, HashSet<EntityKey>>();
if (!relatedEntities.TryGetValue(reference, out entityKeys))
{
entityKeys = new HashSet<EntityKey>();
relatedEntities.Add(reference, entityKeys);
}
entityKeys.Add(foreignKey);
}
}
}
/// <summary>
/// Returns a list of all RelatedEnds for this entity
/// that are the dependent end of an FK Association
/// </summary>
internal IEnumerable<EntityReference> FindFKRelatedEnds()
{
HashSet<EntityReference> relatedEnds = new HashSet<EntityReference>();
foreach (var dependent in ForeignKeyDependents)
{
EntityReference reference = RelationshipManager.GetRelatedEndInternal(
dependent.Item1.ElementType.FullName, dependent.Item2.FromRole.Name) as EntityReference;
relatedEnds.Add(reference);
}
return relatedEnds;
}
/// <summary>
/// Identifies any changes in FK's and creates entries in;
/// - TransactionManager.AddedRelationshipsByForeignKey
/// - TransactionManager.DeletedRelationshipsByForeignKey
///
/// If the FK change will result in fix-up then two entries
/// are added to TransactionManager.AddedRelationshipsByForeignKey
/// (one for each direction of the new realtionship)
/// </summary>
internal void DetectChangesInForeignKeys()
{
//DetectChangesInProperties should already have marked this entity as dirty
Debug.Assert(this.State == EntityState.Added || this.State == EntityState.Modified, "unexpected state");
//We are going to be adding data to the TransactionManager
TransactionManager tm = this.ObjectStateManager.TransactionManager;
foreach (EntityReference entityReference in this.FindFKRelatedEnds())
{
EntityKey currentKey = ForeignKeyFactory.CreateKeyFromForeignKeyValues(this, entityReference);
EntityKey originalKey = entityReference.CachedForeignKey;
bool originalKeyIsConceptualNull = ForeignKeyFactory.IsConceptualNullKey(originalKey);
//If both keys are null there is nothing to check
if (originalKey != null || currentKey != null)
{
if (originalKey == null)
{
//If original is null then we are just adding a relationship
EntityEntry entry;
this.ObjectStateManager.TryGetEntityEntry(currentKey, out entry);
this.AddRelationshipDetectedByForeignKey(tm.AddedRelationshipsByForeignKey, tm.AddedRelationshipsByPrincipalKey, currentKey, entry, entityReference);
}
else if (currentKey == null)
{
//If current is null we are just deleting a relationship
Debug.Assert(!originalKeyIsConceptualNull, "If FK is nullable there shouldn't be a conceptual null set");
this.AddDetectedRelationship(tm.DeletedRelationshipsByForeignKey, originalKey, entityReference);
}
//If there is a Conceptual Null set we need to check if the current values
//are different from the values when the Conceptual Null was created
else if (!currentKey.Equals(originalKey)
&& (!originalKeyIsConceptualNull || ForeignKeyFactory.IsConceptualNullKeyChanged(originalKey, currentKey)))
{
//If keys don't match then we are always adding
EntityEntry entry;
this.ObjectStateManager.TryGetEntityEntry(currentKey, out entry);
this.AddRelationshipDetectedByForeignKey(tm.AddedRelationshipsByForeignKey, tm.AddedRelationshipsByPrincipalKey, currentKey, entry, entityReference);
//And if the original key wasn't a conceptual null we are also deleting
if (!originalKeyIsConceptualNull)
{
this.AddDetectedRelationship(tm.DeletedRelationshipsByForeignKey, originalKey, entityReference);
}
}
}
}
}
/// <summary>
/// True if the underlying entity is not capable of tracking changes to complex types such that
/// DetectChanges is required to do this.
/// </summary>
internal bool RequiresComplexChangeTracking
{
get { return _requiresComplexChangeTracking; }
}
/// <summary>
/// True if the underlying entity is not capable of tracking changes to scalars such that
/// DetectChanges is required to do this.
/// </summary>
internal bool RequiresScalarChangeTracking
{
get { return _requiresScalarChangeTracking; }
}
/// <summary>
/// True if the underlying entity is not capable of performing full change tracking such that
/// it must be considered by at least some parts of DetectChanges.
/// </summary>
internal bool RequiresAnyChangeTracking
{
get { return _requiresAnyChangeTracking; }
}
}
}
|