RelationshipConstraintValidator.cs source code in C# .NET

Source code for the .NET framework in C#

                        

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 Dictionary m_existingRelationships; 

            /// 
            /// Relationships the validator determines are required based on registered entities.
            ///  
            private readonly Dictionary m_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
                Dictionary endNameToKeyMap = 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 (KeyValuePair expected 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 IEnumerable GetReferencingAssocationSets(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 IEnumerable GetEquivalenceSet()
                { 
                    // 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 Dictionary m_existingRelationships; 

            /// 
            /// Relationships the validator determines are required based on registered entities.
            ///  
            private readonly Dictionary m_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
                Dictionary endNameToKeyMap = 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 (KeyValuePair expected 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 IEnumerable GetReferencingAssocationSets(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 IEnumerable GetEquivalenceSet()
                { 
                    // 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

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK