Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / ndp / fx / src / DataEntity / System / Data / Map / Update / Internal / RelationshipConstraintValidator.cs / 1 / RelationshipConstraintValidator.cs
//---------------------------------------------------------------------- //// Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- using System.Data.Metadata.Edm; using System.Data.Objects; using System.Collections.Generic; using System.Data.Common.Utils; using System.Diagnostics; using System.Data.Common; using System.Data.Objects.DataClasses; using System.Globalization; using System.Data.Entity; using System.Linq; namespace System.Data.Mapping.Update.Internal { internal partial class UpdateTranslator { ////// Class validating relationship cardinality constraints. Only reasons about constraints that can be inferred /// by examining change requests from the store. /// (no attempt is made to ensure consistency of the store subsequently, since this would require pulling in all /// values from the store). /// private class RelationshipConstraintValidator { #region Constructor internal RelationshipConstraintValidator(UpdateTranslator updateTranslator) { m_existingRelationships = new Dictionary(EqualityComparer .Default); m_impliedRelationships = new Dictionary (EqualityComparer .Default); m_referencingRelationshipSets = new Dictionary >(EqualityComparer .Default); m_updateTranslator = updateTranslator; } #endregion #region Fields /// /// Relationships registered in the validator. /// private readonly Dictionarym_existingRelationships; /// /// Relationships the validator determines are required based on registered entities. /// private readonly Dictionarym_impliedRelationships; /// /// Cache used to store relationship sets with ends bound to entity sets. /// private readonly Dictionary> m_referencingRelationshipSets; /// /// Update translator containing session context. /// private readonly UpdateTranslator m_updateTranslator; #endregion #region Methods ////// Add an entity to be tracked by the validator. Requires that the input describes an entity. /// /// State entry for the entity being tracked. internal void RegisterEntity(IEntityStateEntry stateEntry) { EntityUtil.CheckArgumentNull(stateEntry, "stateEntry"); if (EntityState.Added == stateEntry.State || EntityState.Deleted == stateEntry.State) { // We only track added and deleted entities because modifications to entities do not affect // cardinality constraints. Relationships are based on end keys, and it is not // possible to modify key values. Debug.Assert(null != (object)stateEntry.EntityKey, "entity state entry must have an entity key"); EntityKey entityKey = EntityUtil.CheckArgumentNull(stateEntry.EntityKey, "stateEntry.EntityKey"); EntitySet entitySet = (EntitySet)stateEntry.EntitySet; EntityType entityType = EntityState.Added == stateEntry.State ? GetEntityType(stateEntry.CurrentValues) : GetEntityType(stateEntry.OriginalValues); // figure out relationship set ends that are associated with this entity set foreach (AssociationSet associationSet in GetReferencingAssocationSets(entitySet)) { // describe unidirectional relationships in which the added entity is the "destination" var ends = associationSet.AssociationSetEnds; foreach (var fromEnd in ends) { foreach (var toEnd in ends) { // end to itself does not describe an interesting relationship subpart if (object.ReferenceEquals(toEnd.CorrespondingAssociationEndMember, fromEnd.CorrespondingAssociationEndMember)) { continue; } // skip ends that don't target the current entity set if (!toEnd.EntitySet.EdmEquals(entitySet)) { continue; } // skip ends that aren't required if (0 == MetadataHelper.GetLowerBoundOfMultiplicity( fromEnd.CorrespondingAssociationEndMember.RelationshipMultiplicity)) { continue; } // skip ends that don't target the current entity type if (!MetadataHelper.GetEntityTypeForEnd(toEnd.CorrespondingAssociationEndMember) .IsAssignableFrom(entityType)) { continue; } // register the relationship so that we know it's required DirectionalRelationship relationship = new DirectionalRelationship(entityKey, fromEnd.CorrespondingAssociationEndMember, toEnd.CorrespondingAssociationEndMember, associationSet, stateEntry); m_impliedRelationships.Add(relationship, stateEntry); } } } } } // requires: input is an IExtendedDataRecord representing an entity // returns: entity type for the given record private static EntityType GetEntityType(DbDataRecord dbDataRecord) { IExtendedDataRecord extendedRecord = dbDataRecord as IExtendedDataRecord; Debug.Assert(extendedRecord != null); Debug.Assert(BuiltInTypeKind.EntityType == extendedRecord.DataRecordInfo.RecordType.EdmType.BuiltInTypeKind); return (EntityType)extendedRecord.DataRecordInfo.RecordType.EdmType; } ////// Add a relationship to be tracked by the validator. /// /// Relationship set to which the given record belongs. /// Relationship record. Must conform to the type of the relationship set. /// State entry for the relationship being tracked internal void RegisterAssociation(AssociationSet associationSet, IExtendedDataRecord record, IEntityStateEntry stateEntry) { EntityUtil.CheckArgumentNull(associationSet, "relationshipSet"); EntityUtil.CheckArgumentNull(record, "record"); EntityUtil.CheckArgumentNull(stateEntry, "stateEntry"); Debug.Assert(associationSet.ElementType.Equals(record.DataRecordInfo.RecordType.EdmType)); // retrieve the ends of the relationship DictionaryendNameToKeyMap = new Dictionary ( StringComparer.Ordinal); foreach (FieldMetadata field in record.DataRecordInfo.FieldMetadata) { string endName = field.FieldType.Name; EntityKey entityKey = (EntityKey)record.GetValue(field.Ordinal); endNameToKeyMap.Add(endName, entityKey); } // register each unidirectional relationship subpart in the relationship instance var ends = associationSet.AssociationSetEnds; foreach (var fromEnd in ends) { foreach (var toEnd in ends) { // end to itself does not describe an interesting relationship subpart if (object.ReferenceEquals(toEnd.CorrespondingAssociationEndMember, fromEnd.CorrespondingAssociationEndMember)) { continue; } EntityKey toEntityKey = endNameToKeyMap[toEnd.CorrespondingAssociationEndMember.Name]; DirectionalRelationship relationship = new DirectionalRelationship(toEntityKey, fromEnd.CorrespondingAssociationEndMember, toEnd.CorrespondingAssociationEndMember, associationSet, stateEntry); AddExistingRelationship(relationship); } } } /// /// Validates cardinality constraints for all added entities/relationships. /// internal void ValidateConstraints() { // ensure all expected relationships exist foreach (KeyValuePairexpected in m_impliedRelationships) { DirectionalRelationship expectedRelationship = expected.Key; IEntityStateEntry stateEntry = expected.Value; // determine actual end cardinality int count = GetDirectionalRelationshipCountDelta(expectedRelationship); if (EntityState.Deleted == stateEntry.State) { // our cardinality expectations are reversed for delete (cardinality of 1 indicates // we want -1 operation total) count = -count; } // determine expected cardinality int minimumCount = MetadataHelper.GetLowerBoundOfMultiplicity(expectedRelationship.FromEnd.RelationshipMultiplicity); int? maximumCountDeclared = MetadataHelper.GetUpperBoundOfMultiplicity(expectedRelationship.FromEnd.RelationshipMultiplicity); int maximumCount = maximumCountDeclared.HasValue ? maximumCountDeclared.Value : count; // negative value // indicates unlimited cardinality if (count < minimumCount || count > maximumCount) { // We could in theory "fix" the cardinality constraint violation by introducing surrogates, // but we risk doing work on behalf of the user they don't want performed (e.g., deleting an // entity or relationship the user has intentionally left untouched). throw EntityUtil.UpdateRelationshipCardinalityConstraintViolation( expectedRelationship.AssociationSet.Name, minimumCount, maximumCountDeclared, TypeHelpers.GetFullName(expectedRelationship.ToEntityKey.EntityContainerName, expectedRelationship.ToEntityKey.EntitySetName), count, expectedRelationship.FromEnd.Name, stateEntry); } } // ensure actual relationships have required ends foreach (DirectionalRelationship actualRelationship in m_existingRelationships.Keys) { int addedCount; int deletedCount; actualRelationship.GetCountsInEquivalenceSet(out addedCount, out deletedCount); int absoluteCount = Math.Abs(addedCount - deletedCount); int minimumCount = MetadataHelper.GetLowerBoundOfMultiplicity(actualRelationship.FromEnd.RelationshipMultiplicity); int? maximumCount = MetadataHelper.GetUpperBoundOfMultiplicity(actualRelationship.FromEnd.RelationshipMultiplicity); // Check that we haven't inserted or deleted too many relationships if (maximumCount.HasValue) { EntityState? violationType = default(EntityState?); int? violationCount = default(int?); if (addedCount > maximumCount.Value) { violationType = EntityState.Added; violationCount = addedCount; } else if (deletedCount > maximumCount.Value) { violationType = EntityState.Deleted; violationCount = deletedCount; } if (violationType.HasValue) { throw EntityUtil.Update(Strings.Update_RelationshipCardinalityViolation(maximumCount.Value, violationType.Value, actualRelationship.AssociationSet.ElementType.FullName, actualRelationship.FromEnd.Name, actualRelationship.ToEnd.Name, violationCount.Value), null, actualRelationship.GetEquivalenceSet().Select(reln => reln.StateEntry)); } } // We care about the case where there is a relationship but no entity when // the relationship and entity map to the same table. If there is a relationship // with 1..1 cardinality to the entity and the relationship is being added or deleted, // it is required that the entity is also added or deleted. if (1 == absoluteCount && 1 == minimumCount && 1 == maximumCount) // 1..1 relationship being added/deleted { bool isAdd = addedCount > deletedCount; // Ensure the entity is also being added or deleted IEntityStateEntry entityEntry; // Identify the following error conditions: // - the entity is not being modified at all // - the entity is being modified, but not in the way we expect (it's not being added or deleted) if (!m_impliedRelationships.TryGetValue(actualRelationship, out entityEntry) || (isAdd && EntityState.Added != entityEntry.State) || (!isAdd && EntityState.Deleted != entityEntry.State)) { throw EntityUtil.UpdateEntityMissingConstraintViolation(actualRelationship.AssociationSet.Name, actualRelationship.ToEnd.Name, actualRelationship.StateEntry); } } } } /// /// Determines the net change in relationship count. /// For instance, if the directional relationship is added 2 times and deleted 3, the return value is -1. /// private int GetDirectionalRelationshipCountDelta(DirectionalRelationship expectedRelationship) { // lookup up existing relationship from expected relationship DirectionalRelationship existingRelationship; if (m_existingRelationships.TryGetValue(expectedRelationship, out existingRelationship)) { int addedCount; int deletedCount; existingRelationship.GetCountsInEquivalenceSet(out addedCount, out deletedCount); return addedCount - deletedCount; } else { // no modifications to the relationship... return 0 (no net change) return 0; } } private void AddExistingRelationship(DirectionalRelationship relationship) { DirectionalRelationship existingRelationship; if (m_existingRelationships.TryGetValue(relationship, out existingRelationship)) { existingRelationship.AddToEquivalenceSet(relationship); } else { m_existingRelationships.Add(relationship, relationship); } } ////// Determine which relationship sets reference the given entity set. /// /// Entity set for which to identify relationships ///Relationship sets referencing the given entity set private IEnumerableGetReferencingAssocationSets(EntitySet entitySet) { List relationshipSets; // check if this information is cached if (!m_referencingRelationshipSets.TryGetValue(entitySet, out relationshipSets)) { relationshipSets = new List (); // relationship sets must live in the same container as the entity sets they reference EntityContainer container = entitySet.EntityContainer; foreach (EntitySetBase extent in container.BaseEntitySets) { AssociationSet associationSet = extent as AssociationSet; if (null != associationSet) { foreach (var end in associationSet.AssociationSetEnds) { if (end.EntitySet.Equals(entitySet)) { relationshipSets.Add(associationSet); break; } } } } // add referencing relationship information to the cache m_referencingRelationshipSets.Add(entitySet, relationshipSets); } return relationshipSets; } #endregion #region Nested types /// /// An instance of an actual or expected relationship. This class describes one direction /// of the relationship. /// private class DirectionalRelationship : IEquatable{ /// /// Entity key for the entity being referenced by the relationship. /// internal readonly EntityKey ToEntityKey; ////// Name of the end referencing the entity key. /// internal readonly AssociationEndMember FromEnd; ////// Name of the end the entity key references. /// internal readonly AssociationEndMember ToEnd; ////// State entry containing this relationship. /// internal readonly IEntityStateEntry StateEntry; ////// Reference to the relationship set. /// internal readonly AssociationSet AssociationSet; ////// Reference to next 'equivalent' relationship in circular linked list. /// private DirectionalRelationship _equivalenceSetLinkedListNext; private readonly int _hashCode; internal DirectionalRelationship(EntityKey toEntityKey, AssociationEndMember fromEnd, AssociationEndMember toEnd, AssociationSet associationSet, IEntityStateEntry stateEntry) { ToEntityKey = EntityUtil.CheckArgumentNull(toEntityKey, "toEntityKey"); FromEnd = EntityUtil.CheckArgumentNull(fromEnd, "fromEnd"); ToEnd = EntityUtil.CheckArgumentNull(toEnd, "toEnd"); AssociationSet = EntityUtil.CheckArgumentNull(associationSet, "associationSet"); StateEntry = EntityUtil.CheckArgumentNull(stateEntry, "stateEntry"); _equivalenceSetLinkedListNext = this; _hashCode = toEntityKey.GetHashCode() ^ fromEnd.GetHashCode() ^ toEnd.GetHashCode() ^ associationSet.GetHashCode(); } ////// Requires: 'other' must refer to the same relationship metadata and the same target entity and /// must not already be a part of an equivalent set. /// Adds the given relationship to linked list containing all equivalent relationship instances /// for this relationship (e.g. all orders associated with a specific customer) /// internal void AddToEquivalenceSet(DirectionalRelationship other) { Debug.Assert(null != other, "other must not be null"); Debug.Assert(this.Equals(other), "other must be another instance of the same relationship target"); Debug.Assert(Object.ReferenceEquals(other._equivalenceSetLinkedListNext, other), "other must not be part of an equivalence set yet"); DirectionalRelationship currentSuccessor = this._equivalenceSetLinkedListNext; this._equivalenceSetLinkedListNext = other; other._equivalenceSetLinkedListNext = currentSuccessor; } ////// Returns all relationships in equivalence set. /// internal IEnumerableGetEquivalenceSet() { // yield everything in circular linked list DirectionalRelationship current = this; do { yield return current; current = current._equivalenceSetLinkedListNext; } while (!object.ReferenceEquals(current, this)); } /// /// Determines the number of add and delete operations contained in this equivalence set. /// internal void GetCountsInEquivalenceSet(out int addedCount, out int deletedCount) { addedCount = 0; deletedCount = 0; // yield everything in circular linked list DirectionalRelationship current = this; do { if (current.StateEntry.State == EntityState.Added) { addedCount++; } else if (current.StateEntry.State == EntityState.Deleted) { deletedCount++; } current = current._equivalenceSetLinkedListNext; } while (!object.ReferenceEquals(current, this)); } public override int GetHashCode() { return _hashCode; } public bool Equals(DirectionalRelationship other) { if (object.ReferenceEquals(this, other)) { return true; } if (null == other) { return false; } if (ToEntityKey != other.ToEntityKey) { return false; } if (AssociationSet != other.AssociationSet) { return false; } if (ToEnd != other.ToEnd) { return false; } if (FromEnd != other.FromEnd) { return false; } return true; } public override bool Equals(object obj) { Debug.Fail("use only typed Equals method"); return Equals(obj as DirectionalRelationship); } public override string ToString() { return String.Format(CultureInfo.InvariantCulture, "{0}.{1}-->{2}: {3}", AssociationSet.Name, FromEnd.Name, ToEnd.Name, StringUtil.BuildDelimitedList(ToEntityKey.EntityKeyValues, null, null)); } } #endregion } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. //---------------------------------------------------------------------- //// Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- using System.Data.Metadata.Edm; using System.Data.Objects; using System.Collections.Generic; using System.Data.Common.Utils; using System.Diagnostics; using System.Data.Common; using System.Data.Objects.DataClasses; using System.Globalization; using System.Data.Entity; using System.Linq; namespace System.Data.Mapping.Update.Internal { internal partial class UpdateTranslator { ////// Class validating relationship cardinality constraints. Only reasons about constraints that can be inferred /// by examining change requests from the store. /// (no attempt is made to ensure consistency of the store subsequently, since this would require pulling in all /// values from the store). /// private class RelationshipConstraintValidator { #region Constructor internal RelationshipConstraintValidator(UpdateTranslator updateTranslator) { m_existingRelationships = new Dictionary(EqualityComparer .Default); m_impliedRelationships = new Dictionary (EqualityComparer .Default); m_referencingRelationshipSets = new Dictionary >(EqualityComparer .Default); m_updateTranslator = updateTranslator; } #endregion #region Fields /// /// Relationships registered in the validator. /// private readonly Dictionarym_existingRelationships; /// /// Relationships the validator determines are required based on registered entities. /// private readonly Dictionarym_impliedRelationships; /// /// Cache used to store relationship sets with ends bound to entity sets. /// private readonly Dictionary> m_referencingRelationshipSets; /// /// Update translator containing session context. /// private readonly UpdateTranslator m_updateTranslator; #endregion #region Methods ////// Add an entity to be tracked by the validator. Requires that the input describes an entity. /// /// State entry for the entity being tracked. internal void RegisterEntity(IEntityStateEntry stateEntry) { EntityUtil.CheckArgumentNull(stateEntry, "stateEntry"); if (EntityState.Added == stateEntry.State || EntityState.Deleted == stateEntry.State) { // We only track added and deleted entities because modifications to entities do not affect // cardinality constraints. Relationships are based on end keys, and it is not // possible to modify key values. Debug.Assert(null != (object)stateEntry.EntityKey, "entity state entry must have an entity key"); EntityKey entityKey = EntityUtil.CheckArgumentNull(stateEntry.EntityKey, "stateEntry.EntityKey"); EntitySet entitySet = (EntitySet)stateEntry.EntitySet; EntityType entityType = EntityState.Added == stateEntry.State ? GetEntityType(stateEntry.CurrentValues) : GetEntityType(stateEntry.OriginalValues); // figure out relationship set ends that are associated with this entity set foreach (AssociationSet associationSet in GetReferencingAssocationSets(entitySet)) { // describe unidirectional relationships in which the added entity is the "destination" var ends = associationSet.AssociationSetEnds; foreach (var fromEnd in ends) { foreach (var toEnd in ends) { // end to itself does not describe an interesting relationship subpart if (object.ReferenceEquals(toEnd.CorrespondingAssociationEndMember, fromEnd.CorrespondingAssociationEndMember)) { continue; } // skip ends that don't target the current entity set if (!toEnd.EntitySet.EdmEquals(entitySet)) { continue; } // skip ends that aren't required if (0 == MetadataHelper.GetLowerBoundOfMultiplicity( fromEnd.CorrespondingAssociationEndMember.RelationshipMultiplicity)) { continue; } // skip ends that don't target the current entity type if (!MetadataHelper.GetEntityTypeForEnd(toEnd.CorrespondingAssociationEndMember) .IsAssignableFrom(entityType)) { continue; } // register the relationship so that we know it's required DirectionalRelationship relationship = new DirectionalRelationship(entityKey, fromEnd.CorrespondingAssociationEndMember, toEnd.CorrespondingAssociationEndMember, associationSet, stateEntry); m_impliedRelationships.Add(relationship, stateEntry); } } } } } // requires: input is an IExtendedDataRecord representing an entity // returns: entity type for the given record private static EntityType GetEntityType(DbDataRecord dbDataRecord) { IExtendedDataRecord extendedRecord = dbDataRecord as IExtendedDataRecord; Debug.Assert(extendedRecord != null); Debug.Assert(BuiltInTypeKind.EntityType == extendedRecord.DataRecordInfo.RecordType.EdmType.BuiltInTypeKind); return (EntityType)extendedRecord.DataRecordInfo.RecordType.EdmType; } ////// Add a relationship to be tracked by the validator. /// /// Relationship set to which the given record belongs. /// Relationship record. Must conform to the type of the relationship set. /// State entry for the relationship being tracked internal void RegisterAssociation(AssociationSet associationSet, IExtendedDataRecord record, IEntityStateEntry stateEntry) { EntityUtil.CheckArgumentNull(associationSet, "relationshipSet"); EntityUtil.CheckArgumentNull(record, "record"); EntityUtil.CheckArgumentNull(stateEntry, "stateEntry"); Debug.Assert(associationSet.ElementType.Equals(record.DataRecordInfo.RecordType.EdmType)); // retrieve the ends of the relationship DictionaryendNameToKeyMap = new Dictionary ( StringComparer.Ordinal); foreach (FieldMetadata field in record.DataRecordInfo.FieldMetadata) { string endName = field.FieldType.Name; EntityKey entityKey = (EntityKey)record.GetValue(field.Ordinal); endNameToKeyMap.Add(endName, entityKey); } // register each unidirectional relationship subpart in the relationship instance var ends = associationSet.AssociationSetEnds; foreach (var fromEnd in ends) { foreach (var toEnd in ends) { // end to itself does not describe an interesting relationship subpart if (object.ReferenceEquals(toEnd.CorrespondingAssociationEndMember, fromEnd.CorrespondingAssociationEndMember)) { continue; } EntityKey toEntityKey = endNameToKeyMap[toEnd.CorrespondingAssociationEndMember.Name]; DirectionalRelationship relationship = new DirectionalRelationship(toEntityKey, fromEnd.CorrespondingAssociationEndMember, toEnd.CorrespondingAssociationEndMember, associationSet, stateEntry); AddExistingRelationship(relationship); } } } /// /// Validates cardinality constraints for all added entities/relationships. /// internal void ValidateConstraints() { // ensure all expected relationships exist foreach (KeyValuePairexpected in m_impliedRelationships) { DirectionalRelationship expectedRelationship = expected.Key; IEntityStateEntry stateEntry = expected.Value; // determine actual end cardinality int count = GetDirectionalRelationshipCountDelta(expectedRelationship); if (EntityState.Deleted == stateEntry.State) { // our cardinality expectations are reversed for delete (cardinality of 1 indicates // we want -1 operation total) count = -count; } // determine expected cardinality int minimumCount = MetadataHelper.GetLowerBoundOfMultiplicity(expectedRelationship.FromEnd.RelationshipMultiplicity); int? maximumCountDeclared = MetadataHelper.GetUpperBoundOfMultiplicity(expectedRelationship.FromEnd.RelationshipMultiplicity); int maximumCount = maximumCountDeclared.HasValue ? maximumCountDeclared.Value : count; // negative value // indicates unlimited cardinality if (count < minimumCount || count > maximumCount) { // We could in theory "fix" the cardinality constraint violation by introducing surrogates, // but we risk doing work on behalf of the user they don't want performed (e.g., deleting an // entity or relationship the user has intentionally left untouched). throw EntityUtil.UpdateRelationshipCardinalityConstraintViolation( expectedRelationship.AssociationSet.Name, minimumCount, maximumCountDeclared, TypeHelpers.GetFullName(expectedRelationship.ToEntityKey.EntityContainerName, expectedRelationship.ToEntityKey.EntitySetName), count, expectedRelationship.FromEnd.Name, stateEntry); } } // ensure actual relationships have required ends foreach (DirectionalRelationship actualRelationship in m_existingRelationships.Keys) { int addedCount; int deletedCount; actualRelationship.GetCountsInEquivalenceSet(out addedCount, out deletedCount); int absoluteCount = Math.Abs(addedCount - deletedCount); int minimumCount = MetadataHelper.GetLowerBoundOfMultiplicity(actualRelationship.FromEnd.RelationshipMultiplicity); int? maximumCount = MetadataHelper.GetUpperBoundOfMultiplicity(actualRelationship.FromEnd.RelationshipMultiplicity); // Check that we haven't inserted or deleted too many relationships if (maximumCount.HasValue) { EntityState? violationType = default(EntityState?); int? violationCount = default(int?); if (addedCount > maximumCount.Value) { violationType = EntityState.Added; violationCount = addedCount; } else if (deletedCount > maximumCount.Value) { violationType = EntityState.Deleted; violationCount = deletedCount; } if (violationType.HasValue) { throw EntityUtil.Update(Strings.Update_RelationshipCardinalityViolation(maximumCount.Value, violationType.Value, actualRelationship.AssociationSet.ElementType.FullName, actualRelationship.FromEnd.Name, actualRelationship.ToEnd.Name, violationCount.Value), null, actualRelationship.GetEquivalenceSet().Select(reln => reln.StateEntry)); } } // We care about the case where there is a relationship but no entity when // the relationship and entity map to the same table. If there is a relationship // with 1..1 cardinality to the entity and the relationship is being added or deleted, // it is required that the entity is also added or deleted. if (1 == absoluteCount && 1 == minimumCount && 1 == maximumCount) // 1..1 relationship being added/deleted { bool isAdd = addedCount > deletedCount; // Ensure the entity is also being added or deleted IEntityStateEntry entityEntry; // Identify the following error conditions: // - the entity is not being modified at all // - the entity is being modified, but not in the way we expect (it's not being added or deleted) if (!m_impliedRelationships.TryGetValue(actualRelationship, out entityEntry) || (isAdd && EntityState.Added != entityEntry.State) || (!isAdd && EntityState.Deleted != entityEntry.State)) { throw EntityUtil.UpdateEntityMissingConstraintViolation(actualRelationship.AssociationSet.Name, actualRelationship.ToEnd.Name, actualRelationship.StateEntry); } } } } /// /// Determines the net change in relationship count. /// For instance, if the directional relationship is added 2 times and deleted 3, the return value is -1. /// private int GetDirectionalRelationshipCountDelta(DirectionalRelationship expectedRelationship) { // lookup up existing relationship from expected relationship DirectionalRelationship existingRelationship; if (m_existingRelationships.TryGetValue(expectedRelationship, out existingRelationship)) { int addedCount; int deletedCount; existingRelationship.GetCountsInEquivalenceSet(out addedCount, out deletedCount); return addedCount - deletedCount; } else { // no modifications to the relationship... return 0 (no net change) return 0; } } private void AddExistingRelationship(DirectionalRelationship relationship) { DirectionalRelationship existingRelationship; if (m_existingRelationships.TryGetValue(relationship, out existingRelationship)) { existingRelationship.AddToEquivalenceSet(relationship); } else { m_existingRelationships.Add(relationship, relationship); } } ////// Determine which relationship sets reference the given entity set. /// /// Entity set for which to identify relationships ///Relationship sets referencing the given entity set private IEnumerableGetReferencingAssocationSets(EntitySet entitySet) { List relationshipSets; // check if this information is cached if (!m_referencingRelationshipSets.TryGetValue(entitySet, out relationshipSets)) { relationshipSets = new List (); // relationship sets must live in the same container as the entity sets they reference EntityContainer container = entitySet.EntityContainer; foreach (EntitySetBase extent in container.BaseEntitySets) { AssociationSet associationSet = extent as AssociationSet; if (null != associationSet) { foreach (var end in associationSet.AssociationSetEnds) { if (end.EntitySet.Equals(entitySet)) { relationshipSets.Add(associationSet); break; } } } } // add referencing relationship information to the cache m_referencingRelationshipSets.Add(entitySet, relationshipSets); } return relationshipSets; } #endregion #region Nested types /// /// An instance of an actual or expected relationship. This class describes one direction /// of the relationship. /// private class DirectionalRelationship : IEquatable{ /// /// Entity key for the entity being referenced by the relationship. /// internal readonly EntityKey ToEntityKey; ////// Name of the end referencing the entity key. /// internal readonly AssociationEndMember FromEnd; ////// Name of the end the entity key references. /// internal readonly AssociationEndMember ToEnd; ////// State entry containing this relationship. /// internal readonly IEntityStateEntry StateEntry; ////// Reference to the relationship set. /// internal readonly AssociationSet AssociationSet; ////// Reference to next 'equivalent' relationship in circular linked list. /// private DirectionalRelationship _equivalenceSetLinkedListNext; private readonly int _hashCode; internal DirectionalRelationship(EntityKey toEntityKey, AssociationEndMember fromEnd, AssociationEndMember toEnd, AssociationSet associationSet, IEntityStateEntry stateEntry) { ToEntityKey = EntityUtil.CheckArgumentNull(toEntityKey, "toEntityKey"); FromEnd = EntityUtil.CheckArgumentNull(fromEnd, "fromEnd"); ToEnd = EntityUtil.CheckArgumentNull(toEnd, "toEnd"); AssociationSet = EntityUtil.CheckArgumentNull(associationSet, "associationSet"); StateEntry = EntityUtil.CheckArgumentNull(stateEntry, "stateEntry"); _equivalenceSetLinkedListNext = this; _hashCode = toEntityKey.GetHashCode() ^ fromEnd.GetHashCode() ^ toEnd.GetHashCode() ^ associationSet.GetHashCode(); } ////// Requires: 'other' must refer to the same relationship metadata and the same target entity and /// must not already be a part of an equivalent set. /// Adds the given relationship to linked list containing all equivalent relationship instances /// for this relationship (e.g. all orders associated with a specific customer) /// internal void AddToEquivalenceSet(DirectionalRelationship other) { Debug.Assert(null != other, "other must not be null"); Debug.Assert(this.Equals(other), "other must be another instance of the same relationship target"); Debug.Assert(Object.ReferenceEquals(other._equivalenceSetLinkedListNext, other), "other must not be part of an equivalence set yet"); DirectionalRelationship currentSuccessor = this._equivalenceSetLinkedListNext; this._equivalenceSetLinkedListNext = other; other._equivalenceSetLinkedListNext = currentSuccessor; } ////// Returns all relationships in equivalence set. /// internal IEnumerableGetEquivalenceSet() { // yield everything in circular linked list DirectionalRelationship current = this; do { yield return current; current = current._equivalenceSetLinkedListNext; } while (!object.ReferenceEquals(current, this)); } /// /// Determines the number of add and delete operations contained in this equivalence set. /// internal void GetCountsInEquivalenceSet(out int addedCount, out int deletedCount) { addedCount = 0; deletedCount = 0; // yield everything in circular linked list DirectionalRelationship current = this; do { if (current.StateEntry.State == EntityState.Added) { addedCount++; } else if (current.StateEntry.State == EntityState.Deleted) { deletedCount++; } current = current._equivalenceSetLinkedListNext; } while (!object.ReferenceEquals(current, this)); } public override int GetHashCode() { return _hashCode; } public bool Equals(DirectionalRelationship other) { if (object.ReferenceEquals(this, other)) { return true; } if (null == other) { return false; } if (ToEntityKey != other.ToEntityKey) { return false; } if (AssociationSet != other.AssociationSet) { return false; } if (ToEnd != other.ToEnd) { return false; } if (FromEnd != other.FromEnd) { return false; } return true; } public override bool Equals(object obj) { Debug.Fail("use only typed Equals method"); return Equals(obj as DirectionalRelationship); } public override string ToString() { return String.Format(CultureInfo.InvariantCulture, "{0}.{1}-->{2}: {3}", AssociationSet.Name, FromEnd.Name, ToEnd.Name, StringUtil.BuildDelimitedList(ToEntityKey.EntityKeyValues, null, null)); } } #endregion } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- CodeFieldReferenceExpression.cs
- Point3DConverter.cs
- UnsafeNativeMethods.cs
- RefExpr.cs
- Transform3DGroup.cs
- FontStretch.cs
- InkCollectionBehavior.cs
- ContainerFilterService.cs
- SortableBindingList.cs
- StringDictionary.cs
- EntityExpressionVisitor.cs
- ColumnResizeAdorner.cs
- UIElementPropertyUndoUnit.cs
- ListBindingConverter.cs
- QueryStringParameter.cs
- Baml6Assembly.cs
- SharedPersonalizationStateInfo.cs
- TraceContext.cs
- SystemColorTracker.cs
- OpCodes.cs
- SerTrace.cs
- EntityDataSourceState.cs
- securitymgrsite.cs
- CompiledRegexRunnerFactory.cs
- EntityDesignerBuildProvider.cs
- KnownColorTable.cs
- BaseConfigurationRecord.cs
- DBParameter.cs
- WebServicesInteroperability.cs
- LoopExpression.cs
- HtmlToClrEventProxy.cs
- RequestResizeEvent.cs
- EntitySqlQueryCacheEntry.cs
- Win32PrintDialog.cs
- ListViewSortEventArgs.cs
- FindProgressChangedEventArgs.cs
- Attributes.cs
- PeerApplication.cs
- RtfFormatStack.cs
- ErasingStroke.cs
- SwitchAttribute.cs
- WizardStepBase.cs
- DataGridPagerStyle.cs
- ImageList.cs
- PropertyToken.cs
- ValueUnavailableException.cs
- CompilationRelaxations.cs
- CharacterShapingProperties.cs
- TakeQueryOptionExpression.cs
- HwndHostAutomationPeer.cs
- LinearKeyFrames.cs
- DataTable.cs
- OdbcException.cs
- FullTextBreakpoint.cs
- UriTemplateClientFormatter.cs
- isolationinterop.cs
- HttpConfigurationSystem.cs
- PageHandlerFactory.cs
- CustomValidator.cs
- DataSourceViewSchemaConverter.cs
- Message.cs
- SafeEventHandle.cs
- SafeHandles.cs
- UpdateInfo.cs
- Mouse.cs
- GuidelineSet.cs
- InkPresenter.cs
- XmlSerializer.cs
- TemplateColumn.cs
- DataGridViewSelectedColumnCollection.cs
- ListViewGroupItemCollection.cs
- PersistNameAttribute.cs
- StrokeCollection.cs
- WmlLabelAdapter.cs
- HttpCapabilitiesBase.cs
- SkewTransform.cs
- XmlStringTable.cs
- HtmlInputControl.cs
- ErrorItem.cs
- ServerIdentity.cs
- PropertyChangedEventArgs.cs
- PageFunction.cs
- QueryAccessibilityHelpEvent.cs
- TypeConverterHelper.cs
- HtmlImage.cs
- Util.cs
- _ConnectionGroup.cs
- PackageRelationshipCollection.cs
- PageCache.cs
- EmbeddedMailObjectsCollection.cs
- CodeMemberProperty.cs
- ObjRef.cs
- DataSourceExpressionCollection.cs
- QilInvokeLateBound.cs
- EntityKey.cs
- TextTreeTextNode.cs
- SizeConverter.cs
- RadioButtonFlatAdapter.cs
- OutputCacheSettings.cs
- JsonReader.cs