internal CurrentValueRecord(ObjectStateEntry cacheEntry, StateManagerTypeMetadata metadata, object userObject)
     :
         base(cacheEntry, metadata, userObject)
 {
 }
Beispiel #2
0
        /// <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();
                        }
                    }
                }
            }
        }
Beispiel #3
0
 internal CurrentValueRecord(ObjectStateEntry cacheEntry, StateManagerTypeMetadata metadata, object userObject)
     :
     base(cacheEntry, metadata, userObject)
 {
 }
Beispiel #4
0
        /// <summary>
        /// Takes key values from the given principal entity and transfers them to the foreign key properties
        /// of the dependant entry.  This method requires a context, but does not require that either
        /// entity is in the context.  This allows it to work in NoTracking cases where we have the context
        /// but we're not tracked by that context.
        /// </summary>
        /// <param name="dependentEntity">The entity into which foreign key values will be written</param>
        /// <param name="principalEntity">The entity from which key values will be obtained</param>
        /// <param name="changedFKs">If non-null, then keeps track of FKs that have already been set such that an exception can be thrown if we find conflicting values</param>
        /// <param name="forceChange">If true, then the property setter is called even if FK values already match,
        ///                           which causes the FK properties to be marked as modified.</param>
        internal void UpdateForeignKeyValues(IEntityWrapper dependentEntity, IEntityWrapper principalEntity, Dictionary <int, object> changedFKs, bool forceChange)
        {
            Debug.Assert(dependentEntity.Entity != null, "dependentEntity.Entity == null");
            Debug.Assert(principalEntity.Entity != null, "principalEntity.Entity == null");
            Debug.Assert(this.IsForeignKey, "cannot update foreign key values if the relationship is not a FK");
            ReferentialConstraint constraint = ((AssociationType)this.RelationMetadata).ReferentialConstraints[0];

            Debug.Assert(constraint != null, "null constraint");

            bool isUnchangedDependent = (object)WrappedOwner.EntityKey != null &&
                                        !WrappedOwner.EntityKey.IsTemporary &&
                                        IsDependentEndOfReferentialConstraint(checkIdentifying: true);

            ObjectStateManager stateManager = ObjectContext.ObjectStateManager;

            stateManager.TransactionManager.BeginForeignKeyUpdate(this);
            try
            {
                EntitySet principalEntitySet = ((AssociationSet)RelationshipSet).AssociationSetEnds[ToEndMember.Name].EntitySet;
                StateManagerTypeMetadata principalTypeMetadata = stateManager.GetOrAddStateManagerTypeMetadata(principalEntity.IdentityType, principalEntitySet);

                EntitySet dependentEntitySet = ((AssociationSet)RelationshipSet).AssociationSetEnds[FromEndProperty.Name].EntitySet;
                StateManagerTypeMetadata dependentTypeMetadata = stateManager.GetOrAddStateManagerTypeMetadata(dependentEntity.IdentityType, dependentEntitySet);

                var      principalProps = constraint.FromProperties;
                int      numValues      = principalProps.Count;
                string[] keyNames       = null;
                object[] values         = null;
                if (numValues > 1)
                {
                    keyNames = principalEntitySet.ElementType.KeyMemberNames;
                    values   = new object[numValues];
                }
                for (int i = 0; i < numValues; i++)
                {
                    int    principalOrdinal = principalTypeMetadata.GetOrdinalforOLayerMemberName(principalProps[i].Name);
                    object value            = principalTypeMetadata.Member(principalOrdinal).GetValue(principalEntity.Entity);
                    int    dependentOrdinal = dependentTypeMetadata.GetOrdinalforOLayerMemberName(constraint.ToProperties[i].Name);
                    bool   valueChanging    = !ByValueEqualityComparer.Default.Equals(dependentTypeMetadata.Member(dependentOrdinal).GetValue(dependentEntity.Entity), value);
                    if (forceChange || valueChanging)
                    {
                        if (isUnchangedDependent)
                        {
                            ValidateSettingRIConstraints(principalEntity, settingToNull: value == null, changingForeignKeyValue: valueChanging);
                        }
                        // If we're tracking FK values that have already been set, then compare the value we are about to set
                        // to the value we previously set for this ordinal, if such a value exists.  If they don't match then
                        // it means that we got conflicting FK values from two different PKs and we should throw.
                        if (changedFKs != null)
                        {
                            object previouslySetValue;
                            if (changedFKs.TryGetValue(dependentOrdinal, out previouslySetValue))
                            {
                                if (!ByValueEqualityComparer.Default.Equals(previouslySetValue, value))
                                {
                                    throw new InvalidOperationException(System.Data.Entity.Strings.Update_ReferentialConstraintIntegrityViolation);
                                }
                            }
                            else
                            {
                                changedFKs[dependentOrdinal] = value;
                            }
                        }
                        dependentEntity.SetCurrentValue(
                            dependentEntity.ObjectStateEntry,
                            dependentTypeMetadata.Member(dependentOrdinal),
                            -1,
                            dependentEntity.Entity,
                            value);
                    }

                    if (numValues > 1)
                    {
                        int keyIndex = Array.IndexOf(keyNames, principalProps[i].Name);
                        Debug.Assert(keyIndex >= 0 && keyIndex < numValues, "Could not find constraint prop name in entity set key names");
                        values[keyIndex] = value;
                    }
                    else
                    {
                        SetCachedForeignKey(new EntityKey(principalEntitySet, value), dependentEntity.ObjectStateEntry);
                    }
                }

                if (numValues > 1)
                {
                    SetCachedForeignKey(new EntityKey(principalEntitySet, values), dependentEntity.ObjectStateEntry);
                }
                if (WrappedOwner.ObjectStateEntry != null)
                {
                    stateManager.ForgetEntryWithConceptualNull(WrappedOwner.ObjectStateEntry, resetAllKeys: false);
                }
            }
            finally
            {
                stateManager.TransactionManager.EndForeignKeyUpdate();
            }
        }