747 lines
30 KiB
C#
747 lines
30 KiB
C#
|
//---------------------------------------------------------------------
|
|||
|
// <copyright file="RelationshipEntry.cs" company="Microsoft">
|
|||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|||
|
// </copyright>
|
|||
|
//
|
|||
|
// @owner [....]
|
|||
|
// @backupOwner [....]
|
|||
|
//---------------------------------------------------------------------
|
|||
|
namespace System.Data.Objects
|
|||
|
{
|
|||
|
using System;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Data.Common;
|
|||
|
using System.Data.Metadata.Edm;
|
|||
|
using System.Data.Objects.DataClasses;
|
|||
|
using System.Data.Objects.Internal;
|
|||
|
using System.Diagnostics;
|
|||
|
|
|||
|
internal sealed class RelationshipEntry : ObjectStateEntry
|
|||
|
{
|
|||
|
internal RelationshipWrapper _relationshipWrapper;
|
|||
|
internal EntityKey Key0 { get { return RelationshipWrapper.Key0; } }
|
|||
|
internal EntityKey Key1 { get { return RelationshipWrapper.Key1; } }
|
|||
|
internal override System.Collections.BitArray ModifiedProperties
|
|||
|
{
|
|||
|
get { return null; }
|
|||
|
}
|
|||
|
|
|||
|
#region Linked list of related relationships
|
|||
|
private RelationshipEntry _nextKey0;
|
|||
|
private RelationshipEntry _nextKey1;
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Constructors
|
|||
|
internal RelationshipEntry(ObjectStateManager cache, EntityState state, RelationshipWrapper relationshipWrapper)
|
|||
|
: base(cache, null, state)
|
|||
|
{
|
|||
|
Debug.Assert(null != relationshipWrapper, "null RelationshipWrapper");
|
|||
|
Debug.Assert(EntityState.Added == state ||
|
|||
|
EntityState.Unchanged == state ||
|
|||
|
EntityState.Deleted == state,
|
|||
|
"invalid EntityState");
|
|||
|
|
|||
|
base._entitySet = relationshipWrapper.AssociationSet;
|
|||
|
_relationshipWrapper = relationshipWrapper;
|
|||
|
}
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Public members
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// API to accept the current values as original values and mark the entity as Unchanged.
|
|||
|
/// </summary>
|
|||
|
/// <param></param>
|
|||
|
/// <returns></returns>
|
|||
|
override public bool IsRelationship
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
ValidateState();
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
override public void AcceptChanges()
|
|||
|
{
|
|||
|
ValidateState();
|
|||
|
|
|||
|
switch (State)
|
|||
|
{
|
|||
|
case EntityState.Deleted:
|
|||
|
DeleteUnnecessaryKeyEntries();
|
|||
|
// 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:
|
|||
|
_cache.ChangeState(this, EntityState.Added, EntityState.Unchanged);
|
|||
|
State = EntityState.Unchanged;
|
|||
|
break;
|
|||
|
case EntityState.Modified:
|
|||
|
Debug.Assert(false, "RelationshipEntry cannot be in Modified state");
|
|||
|
break;
|
|||
|
case EntityState.Unchanged:
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
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);
|
|||
|
}
|
|||
|
|
|||
|
override public IEnumerable<string> GetModifiedProperties()
|
|||
|
{
|
|||
|
ValidateState();
|
|||
|
yield break;
|
|||
|
}
|
|||
|
|
|||
|
override public void SetModified()
|
|||
|
{
|
|||
|
ValidateState();
|
|||
|
throw EntityUtil.CantModifyRelationState();
|
|||
|
}
|
|||
|
|
|||
|
override public object Entity
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
ValidateState();
|
|||
|
return null;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
override public EntityKey EntityKey
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
ValidateState();
|
|||
|
return null;
|
|||
|
}
|
|||
|
internal set
|
|||
|
{
|
|||
|
// no-op for entires other than EntityEntry
|
|||
|
Debug.Assert(false, "EntityKey setter shouldn't be called for RelationshipEntry");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <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)
|
|||
|
{
|
|||
|
ValidateState();
|
|||
|
|
|||
|
throw EntityUtil.CantModifyRelationState();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Throws since the method has no meaning for relationship entries.
|
|||
|
/// </summary>
|
|||
|
override public void RejectPropertyChanges(string propertyName)
|
|||
|
{
|
|||
|
ValidateState();
|
|||
|
|
|||
|
throw EntityUtil.CantModifyRelationState();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Throws since the method has no meaning for relationship entries.
|
|||
|
/// </summary>
|
|||
|
public override bool IsPropertyChanged(string propertyName)
|
|||
|
{
|
|||
|
ValidateState();
|
|||
|
|
|||
|
throw EntityUtil.CantModifyRelationState();
|
|||
|
}
|
|||
|
|
|||
|
/// <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
|
|||
|
{
|
|||
|
ValidateState();
|
|||
|
if (this.State == EntityState.Added)
|
|||
|
{
|
|||
|
throw EntityUtil.OriginalValuesDoesNotExist();
|
|||
|
}
|
|||
|
|
|||
|
return new ObjectStateEntryDbDataRecord(this);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public override OriginalValueRecord GetUpdatableOriginalValues()
|
|||
|
{
|
|||
|
throw EntityUtil.CantModifyRelationValues();
|
|||
|
}
|
|||
|
|
|||
|
/// <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();
|
|||
|
}
|
|||
|
|
|||
|
return new ObjectStateEntryDbUpdatableDataRecord(this);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
override public RelationshipManager RelationshipManager
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
throw new InvalidOperationException(System.Data.Entity.Strings.ObjectStateEntry_RelationshipAndKeyEntriesDoNotHaveRelationshipManagers);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public override void ChangeState(EntityState state)
|
|||
|
{
|
|||
|
EntityUtil.CheckValidStateForChangeRelationshipState(state, "state");
|
|||
|
|
|||
|
if (this.State == EntityState.Detached && state == EntityState.Detached)
|
|||
|
{
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
ValidateState();
|
|||
|
|
|||
|
if (this.RelationshipWrapper.Key0 == this.Key0)
|
|||
|
{
|
|||
|
this.ObjectStateManager.ChangeRelationshipState(
|
|||
|
this.Key0, this.Key1,
|
|||
|
this.RelationshipWrapper.AssociationSet.ElementType.FullName,
|
|||
|
this.RelationshipWrapper.AssociationEndMembers[1].Name,
|
|||
|
state);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Debug.Assert(this.RelationshipWrapper.Key0 == this.Key1, "invalid relationship");
|
|||
|
this.ObjectStateManager.ChangeRelationshipState(
|
|||
|
this.Key0, this.Key1,
|
|||
|
this.RelationshipWrapper.AssociationSet.ElementType.FullName,
|
|||
|
this.RelationshipWrapper.AssociationEndMembers[0].Name,
|
|||
|
state);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public override void ApplyCurrentValues(object currentEntity)
|
|||
|
{
|
|||
|
throw EntityUtil.CantModifyRelationValues();
|
|||
|
}
|
|||
|
|
|||
|
public override void ApplyOriginalValues(object originalEntity)
|
|||
|
{
|
|||
|
throw EntityUtil.CantModifyRelationValues();
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region ObjectStateEntry members
|
|||
|
|
|||
|
override internal bool IsKeyEntry
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
override internal int GetFieldCount(StateManagerTypeMetadata metadata)
|
|||
|
{
|
|||
|
return _relationshipWrapper.AssociationEndMembers.Count;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Reuse or create a new (Entity)DataRecordInfo.
|
|||
|
/// </summary>
|
|||
|
override internal DataRecordInfo GetDataRecordInfo(StateManagerTypeMetadata metadata, object userObject)
|
|||
|
{
|
|||
|
//Dev Note: RelationshipType always has default facets. Thus its safe to construct a TypeUsage from EdmType
|
|||
|
return new DataRecordInfo(TypeUsage.Create(((RelationshipSet)EntitySet).ElementType));
|
|||
|
}
|
|||
|
|
|||
|
override internal void SetModifiedAll()
|
|||
|
{
|
|||
|
ValidateState();
|
|||
|
throw EntityUtil.CantModifyRelationState();
|
|||
|
}
|
|||
|
|
|||
|
override internal Type GetFieldType(int ordinal, StateManagerTypeMetadata metadata)
|
|||
|
{
|
|||
|
// 'metadata' is used for ComplexTypes in EntityEntry
|
|||
|
|
|||
|
return typeof(EntityKey); // this is given By Design
|
|||
|
}
|
|||
|
|
|||
|
override internal string GetCLayerName(int ordinal, StateManagerTypeMetadata metadata)
|
|||
|
{
|
|||
|
ValidateRelationshipRange(ordinal);
|
|||
|
return _relationshipWrapper.AssociationEndMembers[ordinal].Name;
|
|||
|
}
|
|||
|
|
|||
|
override internal int GetOrdinalforCLayerName(string name, StateManagerTypeMetadata metadata)
|
|||
|
{
|
|||
|
AssociationEndMember endMember;
|
|||
|
ReadOnlyMetadataCollection<AssociationEndMember> endMembers = _relationshipWrapper.AssociationEndMembers;
|
|||
|
if (endMembers.TryGetValue(name, false, out endMember))
|
|||
|
{
|
|||
|
return endMembers.IndexOf(endMember);
|
|||
|
}
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
override internal void RevertDelete()
|
|||
|
{
|
|||
|
State = EntityState.Unchanged;
|
|||
|
_cache.ChangeState(this, EntityState.Deleted, State);
|
|||
|
}
|
|||
|
|
|||
|
/// <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)
|
|||
|
{
|
|||
|
throw EntityUtil.CantModifyRelationValues();
|
|||
|
}
|
|||
|
|
|||
|
/// <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)
|
|||
|
{
|
|||
|
throw EntityUtil.CantModifyRelationValues();
|
|||
|
}
|
|||
|
|
|||
|
/// <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)
|
|||
|
{
|
|||
|
throw EntityUtil.CantModifyRelationValues();
|
|||
|
}
|
|||
|
|
|||
|
/// <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)
|
|||
|
{
|
|||
|
throw EntityUtil.CantModifyRelationValues();
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
// Helper method to determine if the specified entityKey is in the given role and AssociationSet in this relationship entry
|
|||
|
internal bool IsSameAssociationSetAndRole(AssociationSet associationSet, AssociationEndMember associationMember, EntityKey entityKey)
|
|||
|
{
|
|||
|
Debug.Assert(associationSet.ElementType.AssociationEndMembers[0].Name == associationMember.Name ||
|
|||
|
associationSet.ElementType.AssociationEndMembers[1].Name == associationMember.Name,
|
|||
|
"Expected associationMember to be one of the ends of the specified associationSet.");
|
|||
|
|
|||
|
if (!Object.ReferenceEquals(_entitySet, associationSet))
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
// Find the end of the relationship that corresponds to the associationMember and see if it matches the EntityKey we are looking for
|
|||
|
if (_relationshipWrapper.AssociationSet.ElementType.AssociationEndMembers[0].Name == associationMember.Name)
|
|||
|
{
|
|||
|
return entityKey == Key0;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
return entityKey == Key1;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private object GetCurrentRelationValue(int ordinal, bool throwException)
|
|||
|
{
|
|||
|
ValidateRelationshipRange(ordinal);
|
|||
|
ValidateState();
|
|||
|
if (State == EntityState.Deleted && throwException)
|
|||
|
{
|
|||
|
throw EntityUtil.CurrentValuesDoesNotExist();
|
|||
|
}
|
|||
|
return _relationshipWrapper.GetEntityKey(ordinal);
|
|||
|
}
|
|||
|
|
|||
|
private static void ValidateRelationshipRange(int ordinal)
|
|||
|
{
|
|||
|
if (unchecked(1u < (uint)ordinal))
|
|||
|
{
|
|||
|
throw EntityUtil.ArgumentOutOfRange("ordinal");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
internal object GetCurrentRelationValue(int ordinal)
|
|||
|
{
|
|||
|
return GetCurrentRelationValue(ordinal, true);
|
|||
|
}
|
|||
|
|
|||
|
internal RelationshipWrapper RelationshipWrapper
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
return _relationshipWrapper;
|
|||
|
}
|
|||
|
set
|
|||
|
{
|
|||
|
Debug.Assert(null != value, "don't set wrapper to null");
|
|||
|
_relationshipWrapper = value;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
override internal void Reset()
|
|||
|
{
|
|||
|
_relationshipWrapper = null;
|
|||
|
|
|||
|
base.Reset();
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Update one of the ends of the relationship
|
|||
|
/// </summary>
|
|||
|
internal void ChangeRelatedEnd(EntityKey oldKey, EntityKey newKey)
|
|||
|
{
|
|||
|
if (oldKey.Equals(Key0))
|
|||
|
{
|
|||
|
if (oldKey.Equals(Key1))
|
|||
|
{ // self-reference
|
|||
|
RelationshipWrapper = new RelationshipWrapper(RelationshipWrapper.AssociationSet, newKey);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
RelationshipWrapper = new RelationshipWrapper(RelationshipWrapper, 0, newKey);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
RelationshipWrapper = new RelationshipWrapper(RelationshipWrapper, 1, newKey);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
internal void DeleteUnnecessaryKeyEntries()
|
|||
|
{
|
|||
|
// We need to check to see if the ends of the relationship are key entries.
|
|||
|
// If they are, and nothing else refers to them then the key entry should be removed.
|
|||
|
for (int i = 0; i < 2; i++)
|
|||
|
{
|
|||
|
EntityKey entityKey = this.GetCurrentRelationValue(i, false) as EntityKey;
|
|||
|
EntityEntry relatedEntry = _cache.GetEntityEntry(entityKey);
|
|||
|
if (relatedEntry.IsKeyEntry)
|
|||
|
{
|
|||
|
bool foundRelationship = false;
|
|||
|
// count the number of relationships this key entry is part of
|
|||
|
// if there aren't any, then the relationship should be deleted
|
|||
|
foreach (RelationshipEntry relationshipEntry in _cache.FindRelationshipsByKey(entityKey))
|
|||
|
{
|
|||
|
// only count relationships that are not the one we are currently deleting (i.e. this)
|
|||
|
if (relationshipEntry != this)
|
|||
|
{
|
|||
|
foundRelationship = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if (!foundRelationship)
|
|||
|
{
|
|||
|
// Nothing is refering to this key entry, so it should be removed from the cache
|
|||
|
_cache.DeleteKeyEntry(relatedEntry);
|
|||
|
// We assume that only one end of relationship can be a key entry,
|
|||
|
// so we can break the loop
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//"doFixup" equals to False is called from EntityCollection & Ref code only
|
|||
|
internal void Delete(bool doFixup)
|
|||
|
{
|
|||
|
ValidateState();
|
|||
|
|
|||
|
if (doFixup)
|
|||
|
{
|
|||
|
if (State != EntityState.Deleted) //for deleted ObjectStateEntry its a no-op
|
|||
|
{
|
|||
|
//Find two ends of the relationship
|
|||
|
EntityEntry entry1 = _cache.GetEntityEntry((EntityKey)GetCurrentRelationValue(0));
|
|||
|
IEntityWrapper wrappedEntity1 = entry1.WrappedEntity;
|
|||
|
EntityEntry entry2 = _cache.GetEntityEntry((EntityKey)GetCurrentRelationValue(1));
|
|||
|
IEntityWrapper wrappedEntity2 = entry2.WrappedEntity;
|
|||
|
|
|||
|
// If one end of the relationship is a KeyEntry, entity1 or entity2 is null.
|
|||
|
// It is not possible that both ends of relationship are KeyEntries.
|
|||
|
if (wrappedEntity1.Entity != null && wrappedEntity2.Entity != null)
|
|||
|
{
|
|||
|
// Obtain the ro role name and relationship name
|
|||
|
// We don't create a full NavigationRelationship here because that would require looking up
|
|||
|
// additional information like property names that we don't need.
|
|||
|
ReadOnlyMetadataCollection<AssociationEndMember> endMembers = _relationshipWrapper.AssociationEndMembers;
|
|||
|
string toRole = endMembers[1].Name;
|
|||
|
string relationshipName = ((AssociationSet)_entitySet).ElementType.FullName;
|
|||
|
wrappedEntity1.RelationshipManager.RemoveEntity(toRole, relationshipName, wrappedEntity2);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// One end of relationship is a KeyEntry, figure out which one is the real entity and get its RelationshipManager
|
|||
|
// so we can update the DetachedEntityKey on the EntityReference associated with this relationship
|
|||
|
EntityKey targetKey = null;
|
|||
|
RelationshipManager relationshipManager = null;
|
|||
|
if (wrappedEntity1.Entity == null)
|
|||
|
{
|
|||
|
targetKey = entry1.EntityKey;
|
|||
|
relationshipManager = wrappedEntity2.RelationshipManager;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
targetKey = entry2.EntityKey;
|
|||
|
relationshipManager = wrappedEntity1.RelationshipManager;
|
|||
|
}
|
|||
|
Debug.Assert(relationshipManager != null, "Entity wrapper returned a null RelationshipManager");
|
|||
|
|
|||
|
// Clear the detachedEntityKey as well. In cases where we have to fix up the detachedEntityKey, we will not always be able to detect
|
|||
|
// if we have *only* a Deleted relationship for a given entity/relationship/role, so clearing this here will ensure that
|
|||
|
// even if no other relationships are added, the key value will still be correct and we won't accidentally pick up an old value.
|
|||
|
|
|||
|
// devnote: Since we know the target end of this relationship is a key entry, it has to be a reference, so just cast
|
|||
|
AssociationEndMember targetMember = this.RelationshipWrapper.GetAssociationEndMember(targetKey);
|
|||
|
EntityReference entityReference = (EntityReference)relationshipManager.GetRelatedEndInternal(targetMember.DeclaringType.FullName, targetMember.Name);
|
|||
|
entityReference.DetachedEntityKey = null;
|
|||
|
|
|||
|
// Now update the state
|
|||
|
if (this.State == EntityState.Added)
|
|||
|
{
|
|||
|
// Remove key entry if necessary
|
|||
|
DeleteUnnecessaryKeyEntries();
|
|||
|
// Remove relationship entry
|
|||
|
// devnote: Using this method instead of just changing the state because the entry
|
|||
|
// may have already been detached along with the key entry above. However,
|
|||
|
// if there were other relationships using the key, it would not have been deleted.
|
|||
|
DetachRelationshipEntry();
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Non-added entries should be deleted
|
|||
|
_cache.ChangeState(this, this.State, EntityState.Deleted);
|
|||
|
State = EntityState.Deleted;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
switch (State)
|
|||
|
{
|
|||
|
case EntityState.Added:
|
|||
|
// Remove key entry if necessary
|
|||
|
DeleteUnnecessaryKeyEntries();
|
|||
|
// Remove relationship entry
|
|||
|
// devnote: Using this method instead of just changing the state because the entry
|
|||
|
// may have already been detached along with the key entry above. However,
|
|||
|
// if there were other relationships using the key, it would not have been deleted.
|
|||
|
DetachRelationshipEntry();
|
|||
|
break;
|
|||
|
case EntityState.Modified:
|
|||
|
Debug.Assert(false, "RelationshipEntry cannot be in Modified state");
|
|||
|
break;
|
|||
|
case EntityState.Unchanged:
|
|||
|
_cache.ChangeState(this, EntityState.Unchanged, EntityState.Deleted);
|
|||
|
State = EntityState.Deleted;
|
|||
|
break;
|
|||
|
//case DataRowState.Deleted: no-op
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
internal object GetOriginalRelationValue(int ordinal)
|
|||
|
{
|
|||
|
return GetCurrentRelationValue(ordinal, false);
|
|||
|
}
|
|||
|
|
|||
|
internal void DetachRelationshipEntry()
|
|||
|
{
|
|||
|
// no-op if already detached
|
|||
|
if (_cache != null)
|
|||
|
{
|
|||
|
_cache.ChangeState(this, this.State, EntityState.Detached);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
internal void ChangeRelationshipState(EntityEntry targetEntry, RelatedEnd relatedEnd, EntityState requestedState)
|
|||
|
{
|
|||
|
Debug.Assert(requestedState != EntityState.Modified, "Invalid requested state for relationsihp");
|
|||
|
Debug.Assert(this.State != EntityState.Modified, "Invalid initial state for relationsihp");
|
|||
|
|
|||
|
EntityState initialState = this.State;
|
|||
|
|
|||
|
switch (initialState)
|
|||
|
{
|
|||
|
case EntityState.Added:
|
|||
|
switch (requestedState)
|
|||
|
{
|
|||
|
case EntityState.Added:
|
|||
|
// no-op
|
|||
|
break;
|
|||
|
case EntityState.Unchanged:
|
|||
|
this.AcceptChanges();
|
|||
|
break;
|
|||
|
case EntityState.Deleted:
|
|||
|
this.AcceptChanges();
|
|||
|
// cascade deletion is not performed because TransactionManager.IsLocalPublicAPI == true
|
|||
|
this.Delete();
|
|||
|
break;
|
|||
|
case EntityState.Detached:
|
|||
|
// cascade deletion is not performed because TransactionManager.IsLocalPublicAPI == true
|
|||
|
this.Delete();
|
|||
|
break;
|
|||
|
default:
|
|||
|
Debug.Assert(false, "Invalid requested state");
|
|||
|
break;
|
|||
|
}
|
|||
|
break;
|
|||
|
case EntityState.Unchanged:
|
|||
|
switch (requestedState)
|
|||
|
{
|
|||
|
case EntityState.Added:
|
|||
|
this.ObjectStateManager.ChangeState(this, EntityState.Unchanged, EntityState.Added);
|
|||
|
this.State = EntityState.Added;
|
|||
|
break;
|
|||
|
case EntityState.Unchanged:
|
|||
|
//no-op
|
|||
|
break;
|
|||
|
case EntityState.Deleted:
|
|||
|
// cascade deletion is not performed because TransactionManager.IsLocalPublicAPI == true
|
|||
|
this.Delete();
|
|||
|
break;
|
|||
|
case EntityState.Detached:
|
|||
|
// cascade deletion is not performed because TransactionManager.IsLocalPublicAPI == true
|
|||
|
this.Delete();
|
|||
|
this.AcceptChanges();
|
|||
|
break;
|
|||
|
default:
|
|||
|
Debug.Assert(false, "Invalid requested state");
|
|||
|
break;
|
|||
|
}
|
|||
|
break;
|
|||
|
case EntityState.Deleted:
|
|||
|
switch (requestedState)
|
|||
|
{
|
|||
|
case EntityState.Added:
|
|||
|
relatedEnd.Add(targetEntry.WrappedEntity,
|
|||
|
applyConstraints: true,
|
|||
|
addRelationshipAsUnchanged: false,
|
|||
|
relationshipAlreadyExists: true,
|
|||
|
allowModifyingOtherEndOfRelationship: false,
|
|||
|
forceForeignKeyChanges: true);
|
|||
|
this.ObjectStateManager.ChangeState(this, EntityState.Deleted, EntityState.Added);
|
|||
|
this.State = EntityState.Added;
|
|||
|
break;
|
|||
|
case EntityState.Unchanged:
|
|||
|
relatedEnd.Add(targetEntry.WrappedEntity,
|
|||
|
applyConstraints: true,
|
|||
|
addRelationshipAsUnchanged: false,
|
|||
|
relationshipAlreadyExists: true,
|
|||
|
allowModifyingOtherEndOfRelationship: false,
|
|||
|
forceForeignKeyChanges: true);
|
|||
|
this.ObjectStateManager.ChangeState(this, EntityState.Deleted, EntityState.Unchanged);
|
|||
|
this.State = EntityState.Unchanged;
|
|||
|
break;
|
|||
|
case EntityState.Deleted:
|
|||
|
// no-op
|
|||
|
break;
|
|||
|
case EntityState.Detached:
|
|||
|
this.AcceptChanges();
|
|||
|
break;
|
|||
|
default:
|
|||
|
Debug.Assert(false, "Invalid requested state");
|
|||
|
break;
|
|||
|
}
|
|||
|
break;
|
|||
|
default:
|
|||
|
Debug.Assert(false, "Invalid entry state");
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
#region RelationshipEnds as singly-linked list
|
|||
|
|
|||
|
internal RelationshipEntry GetNextRelationshipEnd(EntityKey entityKey)
|
|||
|
{
|
|||
|
Debug.Assert(null != (object)entityKey, "null EntityKey");
|
|||
|
Debug.Assert(entityKey.Equals(Key0) || entityKey.Equals(Key1), "EntityKey mismatch");
|
|||
|
return (entityKey.Equals(Key0) ? NextKey0 : NextKey1);
|
|||
|
}
|
|||
|
|
|||
|
internal void SetNextRelationshipEnd(EntityKey entityKey, RelationshipEntry nextEnd)
|
|||
|
{
|
|||
|
Debug.Assert(null != (object)entityKey, "null EntityKey");
|
|||
|
Debug.Assert(entityKey.Equals(Key0) || entityKey.Equals(Key1), "EntityKey mismatch");
|
|||
|
if (entityKey.Equals(Key0))
|
|||
|
{
|
|||
|
NextKey0 = nextEnd;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
NextKey1 = nextEnd;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Use when EntityEntry.EntityKey == this.Wrapper.Key0
|
|||
|
/// </summary>
|
|||
|
internal RelationshipEntry NextKey0
|
|||
|
{
|
|||
|
get { return _nextKey0; }
|
|||
|
set { _nextKey0 = value; }
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Use when EntityEntry.EntityKey == this.Wrapper.Key1
|
|||
|
/// </summary>
|
|||
|
internal RelationshipEntry NextKey1
|
|||
|
{
|
|||
|
get { return _nextKey1; }
|
|||
|
set { _nextKey1 = value; }
|
|||
|
}
|
|||
|
#endregion
|
|||
|
}
|
|||
|
}
|