/// <summary> /// Attempts to null all FKs associated with the dependent end of this relationship on this entity. /// This may result in setting conceptual nulls if the FK is not nullable. /// </summary> internal void NullAllForeignKeys() { Debug.Assert(ObjectContext != null, "Nulling FKs only works when attached."); Debug.Assert(IsForeignKey, "Cannot null FKs for independent associations."); ObjectStateManager stateManager = ObjectContext.ObjectStateManager; EntityEntry entry = WrappedOwner.ObjectStateEntry; TransactionManager transManager = stateManager.TransactionManager; if (!transManager.IsGraphUpdate && !transManager.IsAttachTracking && !transManager.IsRelatedEndAdd) { ReferentialConstraint constraint = ((AssociationType)RelationMetadata).ReferentialConstraints.Single(); if (TargetRoleName == constraint.FromRole.Name) // Only do this on the dependent end { if (transManager.IsDetaching) { // If the principal is being detached, then the dependent must be added back to the // dangling keys index. // Perf note: The dependent currently gets added when it is being detached and is then // removed again later in the process. The code could be optimized to prevent this. Debug.Assert(entry != null, "State entry must exist while detaching."); EntityKey foreignKey = ForeignKeyFactory.CreateKeyFromForeignKeyValues(entry, this); if (foreignKey != null) { stateManager.AddEntryContainingForeignKeyToIndex(foreignKey, entry); } } else if (!ReferenceEquals(stateManager.EntityInvokingFKSetter, WrappedOwner.Entity) && !transManager.IsForeignKeyUpdate) { transManager.BeginForeignKeyUpdate(this); try { bool unableToNull = true; bool canSetModifiedProps = entry != null && (entry.State == EntityState.Modified || entry.State == EntityState.Unchanged); EntitySet dependentEntitySet = ((AssociationSet)RelationshipSet).AssociationSetEnds[FromEndProperty.Name].EntitySet; StateManagerTypeMetadata dependentTypeMetadata = stateManager.GetOrAddStateManagerTypeMetadata(WrappedOwner.IdentityType, dependentEntitySet); for (int i = 0; i < constraint.FromProperties.Count; i++) { string propertyName = constraint.ToProperties[i].Name; int dependentOrdinal = dependentTypeMetadata.GetOrdinalforOLayerMemberName(propertyName); StateManagerMemberMetadata member = dependentTypeMetadata.Member(dependentOrdinal); // This is a check for nullability in o-space. However, o-space nullability is not the // same as nullability of the underlying type. In particular, one difference is that when // attribute-based mapping is used then a property can be marked as not nullable in o-space // even when the underlying CLR type is nullable. For such a case, we treat the property // as if it were not nullable (since that's what we have shipped) even though we could // technically set it to null. if (member.ClrMetadata.Nullable) { // Only set the value to null if it is not already null. if (member.GetValue(WrappedOwner.Entity) != null) { WrappedOwner.SetCurrentValue( WrappedOwner.ObjectStateEntry, dependentTypeMetadata.Member(dependentOrdinal), -1, WrappedOwner.Entity, null); } else { // Given that the current value is null, this next check confirms that the original // value is also null. If it isn't, then we must make sure that the entity is marked // as modified. // This case can happen because fixup in the entity can set the FK to null while processing // a RelatedEnd operation. This will be detected by DetectChanges, but when performing // RelatedEnd operations the user is not required to call DetectChanges. if (canSetModifiedProps && WrappedOwner.ObjectStateEntry.OriginalValues.GetValue(dependentOrdinal) != null) { entry.SetModifiedProperty(propertyName); } } unableToNull = false; } else if (canSetModifiedProps) { entry.SetModifiedProperty(propertyName); } } if (unableToNull) { // We were unable to null out the FK because all FK properties were non-nullable. // We need to keep track of this state so that we treat the FK as null even though // we were not able to null it. This prevents the FK from being used for fixup and // also causes an exception to be thrown if an attempt is made to commit in this state. //We should only set a conceptual null if the entity is tracked if (entry != null) { //The CachedForeignKey may be null if we are putting //back a Conceptual Null as part of roll back EntityKey realKey = CachedForeignKey; if (realKey == null) { realKey = ForeignKeyFactory.CreateKeyFromForeignKeyValues(entry, this); } // Note that the realKey can still be null here for a situation where the key is marked not nullable // in o-space and yet the underlying type is nullable and the entity has been added or attached with a null // value for the property. This will cause SaveChanges to throw unless the entity is marked // as deleted before SaveChanges is called, in which case we don't want to set a conceptual // null here as the call might very well succeed in the database since, unless the FK is // a concurrency token, the value we have for it is not used at all for the delete. if (realKey != null) { SetCachedForeignKey(ForeignKeyFactory.CreateConceptualNullKey(realKey), entry); stateManager.RememberEntryWithConceptualNull(entry); } } } else { SetCachedForeignKey(null, entry); } } finally { transManager.EndForeignKeyUpdate(); } } } } }
internal void NullAllForeignKeys() { ObjectStateManager objectStateManager = this.ObjectContext.ObjectStateManager; EntityEntry objectStateEntry = this.WrappedOwner.ObjectStateEntry; TransactionManager transactionManager = objectStateManager.TransactionManager; if (transactionManager.IsGraphUpdate || transactionManager.IsAttachTracking || transactionManager.IsRelatedEndAdd) { return; } ReferentialConstraint referentialConstraint = ((AssociationType)this.RelationMetadata).ReferentialConstraints.Single <ReferentialConstraint>(); if (!(this.TargetRoleName == referentialConstraint.FromRole.Name)) { return; } if (transactionManager.IsDetaching) { EntityKey foreignKeyValues = ForeignKeyFactory.CreateKeyFromForeignKeyValues(objectStateEntry, (RelatedEnd)this); if (!(foreignKeyValues != (EntityKey)null)) { return; } objectStateManager.AddEntryContainingForeignKeyToIndex(this, foreignKeyValues, objectStateEntry); } else { if (object.ReferenceEquals(objectStateManager.EntityInvokingFKSetter, this.WrappedOwner.Entity) || transactionManager.IsForeignKeyUpdate) { return; } transactionManager.BeginForeignKeyUpdate(this); try { bool flag1 = true; bool flag2 = objectStateEntry != null && (objectStateEntry.State == EntityState.Modified || objectStateEntry.State == EntityState.Unchanged); EntitySet entitySet = ((AssociationSet)this.RelationshipSet).AssociationSetEnds[this.FromEndMember.Name].EntitySet; StateManagerTypeMetadata managerTypeMetadata = objectStateManager.GetOrAddStateManagerTypeMetadata(this.WrappedOwner.IdentityType, entitySet); for (int index = 0; index < referentialConstraint.FromProperties.Count; ++index) { string name = referentialConstraint.ToProperties[index].Name; int olayerMemberName = managerTypeMetadata.GetOrdinalforOLayerMemberName(name); StateManagerMemberMetadata managerMemberMetadata = managerTypeMetadata.Member(olayerMemberName); if (managerMemberMetadata.ClrMetadata.Nullable) { if (managerMemberMetadata.GetValue(this.WrappedOwner.Entity) != null) { this.WrappedOwner.SetCurrentValue(this.WrappedOwner.ObjectStateEntry, managerTypeMetadata.Member(olayerMemberName), -1, this.WrappedOwner.Entity, (object)null); } else if (flag2 && this.WrappedOwner.ObjectStateEntry.OriginalValues.GetValue(olayerMemberName) != null) { objectStateEntry.SetModifiedProperty(name); } flag1 = false; } else if (flag2) { objectStateEntry.SetModifiedProperty(name); } } if (flag1) { if (objectStateEntry == null) { return; } EntityKey originalKey = this.CachedForeignKey; if (originalKey == (EntityKey)null) { originalKey = ForeignKeyFactory.CreateKeyFromForeignKeyValues(objectStateEntry, (RelatedEnd)this); } if (!(originalKey != (EntityKey)null)) { return; } this.SetCachedForeignKey(ForeignKeyFactory.CreateConceptualNullKey(originalKey), objectStateEntry); objectStateManager.RememberEntryWithConceptualNull(objectStateEntry); } else { this.SetCachedForeignKey((EntityKey)null, objectStateEntry); } } finally { transactionManager.EndForeignKeyUpdate(); } } }