ChangeProcessor.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 / DLinq / Dlinq / ChangeProcessor.cs / 2 / ChangeProcessor.cs

                            using System; 
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text; 

namespace System.Data.Linq { 
    using System.Data.Linq.Mapping; 
    using System.Data.Linq.Provider;
 
    /// 
    /// Describes the type of change the entity will undergo when submitted to the database.
    /// 
    public enum ChangeAction { 
        /// 
        /// The entity will not be submitted. 
        ///  
        None = 0,
        ///  
        /// The entity will be deleted.
        /// 
        Delete,
        ///  
        /// The entity will be inserted.
        ///  
        Insert, 
        /// 
        /// The entity will be updated. 
        /// 
        Update
    }
 
    internal class ChangeProcessor {
        CommonDataServices services; 
        DataContext context; 
        ChangeTracker tracker;
        ChangeDirector changeDirector; 
        EdgeMap currentParentEdges;
        EdgeMap originalChildEdges;
        ReferenceMap originalChildReferences;
 
        internal ChangeProcessor(CommonDataServices services, DataContext context) {
            this.services = services; 
            this.context = context; 
            this.tracker = services.ChangeTracker;
            this.changeDirector = services.ChangeDirector; 
            this.currentParentEdges = new EdgeMap();
            this.originalChildEdges = new EdgeMap();
            this.originalChildReferences = new ReferenceMap();
        } 

        internal void SubmitChanges(ConflictMode failureMode) { 
            this.TrackUntrackedObjects(); 
            // Must apply inferred deletions only after any untracked objects
            // are tracked 
            this.ApplyInferredDeletions();
            this.BuildEdgeMaps();

            var list = this.GetOrderedList(); 

            ValidateAll(list); 
 
            int numUpdatesAttempted = 0;
            ChangeConflictSession conflictSession = new ChangeConflictSession(this.context); 
            List conflicts = new List();
            List deletedItems = new List();
            List insertedItems = new List();
 
            foreach (TrackedObject item in list) {
                try { 
                    if (item.IsNew) { 
                        item.SynchDependentData();
                        changeDirector.Insert(item); 
                        // store all inserted items for post processing
                        insertedItems.Add(item);
                    }
                    else if (item.IsDeleted) { 
                        // Delete returns 1 if the delete was successfull, 0 if the row exists
                        // but wasn't deleted due to an OC conflict, or -1 if the row was 
                        // deleted by another context (no OC conflict in this case) 
                        numUpdatesAttempted++;
                        int ret = changeDirector.Delete(item); 
                        if (ret == 0) {
                            conflicts.Add(new ObjectChangeConflict(conflictSession, item, false));
                        }
                        else { 
                            // store all deleted items for post processing
                            deletedItems.Add(item); 
                        } 
                    }
                    else if (item.IsPossiblyModified) { 
                        item.SynchDependentData();
                        if (item.IsModified) {
                            CheckForInvalidChanges(item);
                            numUpdatesAttempted++; 
                            if (changeDirector.Update(item) <= 0) {
                                conflicts.Add(new ObjectChangeConflict(conflictSession, item)); 
                            } 
                        }
                    } 
                }
                catch (ChangeConflictException) {
                    conflicts.Add(new ObjectChangeConflict(conflictSession, item));
                } 
                if (conflicts.Count > 0 && failureMode == ConflictMode.FailOnFirstConflict) {
                    break; 
                } 
            }
 
            // if we have accumulated any failed updates, throw the exception now
            if (conflicts.Count > 0) {
                this.context.ChangeConflicts.Fill(conflicts);
                throw CreateChangeConflictException(numUpdatesAttempted, conflicts.Count); 
            }
 
            // Only after all updates have been sucessfully processed do we want to make 
            // post processing modifications to the objects and/or cache state.
            PostProcessUpdates(insertedItems, deletedItems); 
        }

        private void PostProcessUpdates(List insertedItems, List deletedItems) {
            // perform post delete processing 
            foreach (TrackedObject deletedItem in deletedItems) {
                // remove deleted item from identity cache 
                this.services.RemoveCachedObjectLike(deletedItem.Type, deletedItem.Original); 
                ClearForeignKeyReferences(deletedItem);
            } 

            // perform post insert processing
            foreach (TrackedObject insertedItem in insertedItems) {
                object lookup = this.services.InsertLookupCachedObject(insertedItem.Type, insertedItem.Current); 
                if (lookup != insertedItem.Current) {
                    throw new DuplicateKeyException(insertedItem.Current, Strings.DatabaseGeneratedAlreadyExistingKey); 
                } 
                insertedItem.InitializeDeferredLoaders();
            } 
        }

        /// 
        /// For each unidirectional and bi-directional 1:N and 1:1 association of a deleted 
        /// object, we perform the following reference cleanup:
        ///   - for 1:N we remove the deleted entity from the opposite EntitySet or collection 
        ///     and null out the back reference as well as the key fields 
        ///   - for 1:1 we null out the back reference and the key fields
        ///  
        private void ClearForeignKeyReferences(TrackedObject to) {
            foreach (MetaAssociation assoc in to.Type.Associations) {
                // If this is the 1-side of a bidirectional relationship and the relationship is with the parent's primary key,
                // we will try to fixup the relationship references on the objects 
                if (assoc.IsForeignKey && assoc.OtherMember != null && assoc.OtherMember.IsAssociation && assoc.OtherKeyIsPrimaryKey) {
                    // Search the cache for the target of the association, since 
                    // it might not be loaded on the object being deleted, and we 
                    // don't want to force a load.
                    object[] keyValues = CommonDataServices.GetForeignKeyValues(assoc, to.Current); 
                    object cached = this.services.IdentityManager.Find(assoc.OtherType, keyValues);

                    if (cached != null) {
                        if (assoc.OtherMember.Association.IsMany) { 
                            // Note that going through the IList interface handles
                            // EntitySet as well as POCO collections that implement IList 
                            // and are not FixedSize. 
                            System.Collections.IList collection = assoc.OtherMember.MemberAccessor.GetBoxedValue(cached) as System.Collections.IList;
                            if (collection != null && !collection.IsFixedSize) { 
                                collection.Remove(to.Current);
                                ClearForeignKeysHelper(assoc, to.Current);
                            }
                        } 
                        else {
                            // Null out the other association.  Since this is a 1:1 association, 
                            // we're not concerned here with causing a deferred load, since the 
                            // target is already cached (since we're deleting it).
                            assoc.OtherMember.MemberAccessor.SetBoxedValue(ref cached, null); 
                            ClearForeignKeysHelper(assoc, to.Current);
                        }
                    }
                } 
                else if (assoc.IsForeignKey) {
                    // If this is a unidirectional relationship or the relationship is with a unique column on the parent, and not a primary key, 
                    // then we just need to clear the relationship on our side 
                    ClearForeignKeysHelper(assoc, to.Current);
                } 
            }
        }

        private void ClearForeignKeysHelper(MetaAssociation assoc, object trackedInstance) { 
            assoc.ThisMember.MemberAccessor.SetBoxedValue(ref trackedInstance, null);
 
            for (int i = 0, n = assoc.ThisKey.Count; i < n; i++) { 
                MetaDataMember thisKey = assoc.ThisKey[i];
                if (thisKey.CanBeNull) { 
                    thisKey.StorageAccessor.SetBoxedValue(ref trackedInstance, null);
                }
            }
        } 

        private static void ValidateAll(IEnumerable list) { 
            foreach (var item in list) { 
                if (item.IsNew) {
                    item.SynchDependentData(); 
                    if (item.Type.HasAnyValidateMethod) {
                        SendOnValidate(item.Type, item, ChangeAction.Insert);
                    }
                } else if (item.IsDeleted) { 
                    if (item.Type.HasAnyValidateMethod) {
                        SendOnValidate(item.Type, item, ChangeAction.Delete); 
                    } 
                } else if (item.IsPossiblyModified) {
                    item.SynchDependentData(); 
                    if (item.IsModified && item.Type.HasAnyValidateMethod) {
                        SendOnValidate(item.Type, item, ChangeAction.Update);
                    }
                } 
            }
        } 
 
        private static void SendOnValidate(MetaType type, TrackedObject item, ChangeAction changeAction) {
            if (type != null) { 
                SendOnValidate(type.InheritanceBase, item, changeAction);

                if (type.OnValidateMethod != null) {
                    try { 
                        type.OnValidateMethod.Invoke(item.Current, new object[] { changeAction });
                    } catch (TargetInvocationException tie) { 
                        if (tie.InnerException != null) { 
                            throw tie.InnerException;
                        } 

                        throw;
                    }
                } 
            }
        } 
 
        internal string GetChangeText() {
            this.ObserveUntrackedObjects(); 
            // Must apply inferred deletions only after any untracked objects
            // are tracked
            this.ApplyInferredDeletions();
            this.BuildEdgeMaps(); 

            // append change text only 
            StringBuilder changeText = new StringBuilder(); 
            foreach (TrackedObject item in this.GetOrderedList()) {
                if (item.IsNew) { 
                    item.SynchDependentData();
                    changeDirector.AppendInsertText(item, changeText);
                }
                else if (item.IsDeleted) { 
                    changeDirector.AppendDeleteText(item, changeText);
                } 
                else if (item.IsPossiblyModified) { 
                    item.SynchDependentData();
                    if (item.IsModified) { 
                        changeDirector.AppendUpdateText(item, changeText);
                    }
                }
            } 
            return changeText.ToString();
        } 
 
        internal ChangeSet GetChangeSet() {
            List newEntities = new List(); 
            List deletedEntities = new List();
            List changedEntities = new List();

            this.ObserveUntrackedObjects(); 
            // Must apply inferred deletions only after any untracked objects
            // are tracked 
            this.ApplyInferredDeletions(); 

            foreach (TrackedObject item in this.tracker.GetInterestingObjects()) { 
                if (item.IsNew) {
                    item.SynchDependentData();
                    newEntities.Add(item.Current);
                } 
                else if (item.IsDeleted) {
                    deletedEntities.Add(item.Current); 
                } 
                else if (item.IsPossiblyModified) {
                    item.SynchDependentData(); 
                    if (item.IsModified) {
                        changedEntities.Add(item.Current);
                    }
                } 
            }
 
            return new ChangeSet(newEntities.AsReadOnly(), deletedEntities.AsReadOnly(), changedEntities.AsReadOnly()); 
        }
 
        // verify that primary key and db-generated values have not changed
        private static void CheckForInvalidChanges(TrackedObject tracked) {
            foreach (MetaDataMember mem in tracked.Type.PersistentDataMembers) {
                if (mem.IsPrimaryKey || mem.IsDbGenerated || mem.IsVersion) { 
                    if (tracked.HasChangedValue(mem)) {
                        if (mem.IsPrimaryKey) { 
                            throw Error.IdentityChangeNotAllowed(mem.Name, tracked.Type.Name); 
                        }
                        else { 
                            throw Error.DbGeneratedChangeNotAllowed(mem.Name, tracked.Type.Name);
                        }
                    }
                } 
            }
        } 
 
        /// 
        /// Create an ChangeConflictException with the best message 
        /// 
        static private ChangeConflictException CreateChangeConflictException(int totalUpdatesAttempted, int failedUpdates) {
            string msg = Strings.RowNotFoundOrChanged;
            if (totalUpdatesAttempted > 1) { 
                msg = Strings.UpdatesFailedMessage(failedUpdates, totalUpdatesAttempted);
            } 
            return new ChangeConflictException(msg); 
        }
 
        internal void TrackUntrackedObjects() {
            Dictionary visited = new Dictionary();

            // search for untracked new objects 
            List items = new List(this.tracker.GetInterestingObjects());
            foreach (TrackedObject item in items) { 
                this.TrackUntrackedObjects(item.Type, item.Current, visited); 
            }
        } 

        internal void ApplyInferredDeletions() {
            foreach (TrackedObject item in this.tracker.GetInterestingObjects()) {
                if (item.CanInferDelete()) { 
                    // based on DeleteOnNull specifications on the item's associations,
                    // a deletion can be inferred for this item.  The actual state transition 
                    // is dependent on the current item state. 
                    if (item.IsNew) {
                        item.ConvertToRemoved(); 
                    }
                    else if (item.IsPossiblyModified || item.IsModified) {
                        item.ConvertToDeleted();
                    } 
                }
            } 
        } 

        private void TrackUntrackedObjects(MetaType type, object item, Dictionary visited) { 
            if (!visited.ContainsKey(item)) {
                visited.Add(item, item);
                TrackedObject tracked = this.tracker.GetTrackedObject(item);
                if (tracked == null) { 
                    tracked = this.tracker.Track(item);
                    tracked.ConvertToNew(); 
                } 
                else if (tracked.IsDead || tracked.IsRemoved) {
                    // ignore 
                    return;
                }

                // search parents (objects we are dependent on) 
                foreach (RelatedItem parent in this.services.GetParents(type, item)) {
                    this.TrackUntrackedObjects(parent.Type, parent.Item, visited); 
                } 

                // synch up primary key 
                if (tracked.IsNew) {
                    tracked.InitializeDeferredLoaders();

                    if (!tracked.IsPendingGeneration(tracked.Type.IdentityMembers)) { 
                        tracked.SynchDependentData();
                        object cached = this.services.InsertLookupCachedObject(tracked.Type, item); 
                        if (cached != item) { 
                            TrackedObject cachedTracked = this.tracker.GetTrackedObject(cached);
                            System.Diagnostics.Debug.Assert(cachedTracked != null); 
                            if (cachedTracked.IsDeleted || cachedTracked.CanInferDelete()) {
                                // adding new object with same ID as object being deleted.. turn into modified
                                tracked.ConvertToPossiblyModified(cachedTracked.Original);
                                // turn deleted to dead... 
                                cachedTracked.ConvertToDead();
 
                                this.services.RemoveCachedObjectLike(tracked.Type, item); 
                                this.services.InsertLookupCachedObject(tracked.Type, item);
                            } 
                            else if (!cachedTracked.IsDead) {
                                throw new DuplicateKeyException(item, Strings.CantAddAlreadyExistingKey);
                            }
                        } 
                    }
                    else { 
                        // we may have a generated PK, however we set the PK on this new item to 
                        // match a deleted item
                        object cached = this.services.GetCachedObjectLike(tracked.Type, item); 
                        if (cached != null) {
                            TrackedObject cachedTracked = this.tracker.GetTrackedObject(cached);
                            System.Diagnostics.Debug.Assert(cachedTracked != null);
                            if (cachedTracked.IsDeleted || cachedTracked.CanInferDelete()) { 
                                // adding new object with same ID as object being deleted.. turn into modified
                                tracked.ConvertToPossiblyModified(cachedTracked.Original); 
                                // turn deleted to dead... 
                                cachedTracked.ConvertToDead();
 
                                this.services.RemoveCachedObjectLike(tracked.Type, item);
                                this.services.InsertLookupCachedObject(tracked.Type, item);
                            }
                        } 
                    }
                } 
 
                // search children (objects that are dependent on us)
                foreach (RelatedItem child in this.services.GetChildren(type, item)) { 
                    this.TrackUntrackedObjects(child.Type, child.Item, visited);
                }
            }
        } 

        internal void ObserveUntrackedObjects() { 
            Dictionary visited = new Dictionary(); 

            List items = new List(this.tracker.GetInterestingObjects()); 
            foreach (TrackedObject item in items) {
                this.ObserveUntrackedObjects(item.Type, item.Current, visited);
            }
        } 

        private void ObserveUntrackedObjects(MetaType type, object item, Dictionary visited) { 
            if (!visited.ContainsKey(item)) { 
                visited.Add(item, item);
                TrackedObject tracked = this.tracker.GetTrackedObject(item); 
                if (tracked == null) {
                    tracked = this.tracker.Track(item);
                    tracked.ConvertToNew();
                } else if (tracked.IsDead || tracked.IsRemoved) { 
                    // ignore
                    return; 
                } 

                // search parents (objects we are dependent on) 
                foreach (RelatedItem parent in this.services.GetParents(type, item)) {
                    this.ObserveUntrackedObjects(parent.Type, parent.Item, visited);
                }
 
                // synch up primary key unless its generated.
                if (tracked.IsNew) { 
                     if (!tracked.IsPendingGeneration(tracked.Type.IdentityMembers)) { 
                        tracked.SynchDependentData();
                     } 
                }

                // search children (objects that are dependent on us)
                foreach (RelatedItem child in this.services.GetChildren(type, item)) { 
                    this.ObserveUntrackedObjects(child.Type, child.Item, visited);
                } 
            } 
        }
 
        private TrackedObject GetOtherItem(MetaAssociation assoc, object instance) {
            if (instance == null)
                return null;
            object other = null; 
            // Don't load unloaded references
            if (assoc.ThisMember.StorageAccessor.HasAssignedValue(instance) || 
                assoc.ThisMember.StorageAccessor.HasLoadedValue(instance) 
                ) {
                other = assoc.ThisMember.MemberAccessor.GetBoxedValue(instance); 
            }
            else if (assoc.OtherKeyIsPrimaryKey) {
                // Maybe it's in the cache, but not yet attached through reference.
                object[] foreignKeys = CommonDataServices.GetForeignKeyValues(assoc, instance); 
                other = this.services.GetCachedObject(assoc.OtherType, foreignKeys);
            } 
            // else the other key is not the primary key so there is no way to try to look it up 
            return (other != null) ? this.tracker.GetTrackedObject(other) : null;
        } 

        private bool HasAssociationChanged(MetaAssociation assoc, TrackedObject item) {
            if (item.Original != null && item.Current != null) {
                if (assoc.ThisMember.StorageAccessor.HasAssignedValue(item.Current) || 
                    assoc.ThisMember.StorageAccessor.HasLoadedValue(item.Current)
                    ) { 
                    return this.GetOtherItem(assoc, item.Current) != this.GetOtherItem(assoc, item.Original); 
                }
                else { 
                    object[] currentFKs = CommonDataServices.GetForeignKeyValues(assoc, item.Current);
                    object[] originaFKs = CommonDataServices.GetForeignKeyValues(assoc, item.Original);
                    for (int i = 0, n = currentFKs.Length; i < n; i++) {
                        if (!object.Equals(currentFKs[i], originaFKs[i])) 
                            return true;
                    } 
                } 
            }
            return false; 
        }

        private void BuildEdgeMaps() {
            this.currentParentEdges.Clear(); 
            this.originalChildEdges.Clear();
            this.originalChildReferences.Clear(); 
 
            List list = new List(this.tracker.GetInterestingObjects());
            foreach (TrackedObject item in list) { 
                bool isNew = item.IsNew;
                MetaType mt = item.Type;
                foreach (MetaAssociation assoc in mt.Associations) {
                    if (assoc.IsForeignKey) { 
                        TrackedObject otherItem = this.GetOtherItem(assoc, item.Current);
                        TrackedObject dbOtherItem = this.GetOtherItem(assoc, item.Original); 
                        bool pointsToDeleted = (otherItem != null && otherItem.IsDeleted) || (dbOtherItem != null && dbOtherItem.IsDeleted); 
                        bool pointsToNew = (otherItem != null && otherItem.IsNew);
 
                        if (isNew || pointsToDeleted || pointsToNew || this.HasAssociationChanged(assoc, item)) {
                            if (otherItem != null) {
                                this.currentParentEdges.Add(assoc, item, otherItem);
                            } 
                            if (dbOtherItem != null) {
                                if (assoc.IsUnique) { 
                                    this.originalChildEdges.Add(assoc, dbOtherItem, item); 
                                }
                                this.originalChildReferences.Add(dbOtherItem, item); 
                            }
                        }
                    }
                } 
            }
        } 
 
        enum VisitState {
            Before, 
            After
        }

        private List GetOrderedList() { 
            var objects = this.tracker.GetInterestingObjects().ToList();
 
            // give list an initial order (most likely correct order) to avoid deadlocks in server 
            var range = Enumerable.Range(0, objects.Count).ToList();
            range.Sort((int x, int y) => Compare(objects[x], x, objects[y], y)); 
            var ordered = range.Select(i => objects[i]).ToList();

            // permute order if constraint dependencies requires some changes to come before others
            var visited = new Dictionary(); 
            var list = new List();
            foreach (TrackedObject item in ordered) { 
                this.BuildDependencyOrderedList(item, list, visited); 
            }
            return list; 
        }

        private static int Compare(TrackedObject x, int xOrdinal, TrackedObject y, int yOrdinal) {
            // deal with possible nulls 
            if (x == y) {
                return 0; 
            } 
            if (x == null) {
                return -1; 
            }
            else if (y == null) {
                return 1;
            } 
            // first order by action: Inserts first, Updates, Deletes last
            int xAction = x.IsNew ? 0 : x.IsDeleted ? 2 : 1; 
            int yAction = y.IsNew ? 0 : y.IsDeleted ? 2 : 1; 
            if (xAction < yAction) {
                return -1; 
            }
            else if (xAction > yAction) {
                return 1;
            } 
            // no need to order inserts (PK's may not even exist)
            if (x.IsNew) { 
                // keep original order 
                return xOrdinal.CompareTo(yOrdinal);
            } 
            // second order by type
            if (x.Type != y.Type) {
                return string.CompareOrdinal(x.Type.Type.FullName, y.Type.Type.FullName);
            } 
            // lastly, order by PK values
            int result = 0; 
            foreach (MetaDataMember mm in x.Type.IdentityMembers) { 
                object xValue = mm.StorageAccessor.GetBoxedValue(x.Current);
                object yValue = mm.StorageAccessor.GetBoxedValue(y.Current); 
                if (xValue == null) {
                    if (yValue != null) {
                        return -1;
                    } 
                }
                else { 
                    IComparable xc = xValue as IComparable; 
                    if (xc != null) {
                        result = xc.CompareTo(yValue); 
                        if (result != 0) {
                            return result;
                        }
                    } 
                }
            } 
            // they are the same? leave in original order 
            return xOrdinal.CompareTo(yOrdinal);
        } 

        private void BuildDependencyOrderedList(TrackedObject item, List list, Dictionary visited) {
            VisitState state;
            if (visited.TryGetValue(item, out state)) { 
                if (state == VisitState.Before) {
                    throw Error.CycleDetected(); 
                } 
                return;
            } 

            visited[item] = VisitState.Before;

            if (item.IsInteresting) { 
                if (item.IsDeleted) {
                    // if 'item' is deleted 
                    //    all objects that used to refer to 'item' must be ordered before item 
                    foreach (TrackedObject other in this.originalChildReferences[item]) {
                        if (other != item) { 
                            this.BuildDependencyOrderedList(other, list, visited);
                        }
                    }
                } 
                else {
                    // if 'item' is new or changed 
                    //   for all objects 'other' that 'item' refers to along association 'assoc' 
                    //      if 'other' is new then 'other' must be ordered before 'item'
                    //      if 'assoc' is pure one-to-one and some other item 'prevItem' used to refer to 'other' 
                    //         then 'prevItem' must be ordered before 'item'
                    foreach (MetaAssociation assoc in item.Type.Associations) {
                        if (assoc.IsForeignKey) {
                            TrackedObject other = this.currentParentEdges[assoc, item]; 
                            if (other != null) {
                                if (other.IsNew) { 
                                    // if other is new, visit other first (since item's FK depends on it) 
                                    if (other != item || item.Type.DBGeneratedIdentityMember != null) {
                                        this.BuildDependencyOrderedList(other, list, visited); 
                                    }
                                }
                                else if ((assoc.IsUnique || assoc.ThisKeyIsPrimaryKey)) {
                                    TrackedObject prevItem = this.originalChildEdges[assoc, other]; 
                                    if (prevItem != null && other != item) {
                                        this.BuildDependencyOrderedList(prevItem, list, visited); 
                                    } 
                                }
                            } 
                        }
                    }
                }
 
                list.Add(item);
            } 
 
            visited[item] = VisitState.After;
        } 

        class EdgeMap {
            Dictionary> associations;
 
            internal EdgeMap() {
                this.associations = new Dictionary>(); 
            } 

            internal void Add(MetaAssociation assoc, TrackedObject from, TrackedObject to) { 
                Dictionary pairs;
                if (!associations.TryGetValue(assoc, out pairs)) {
                    pairs = new Dictionary();
                    associations.Add(assoc, pairs); 
                }
                pairs.Add(from, to); 
            } 

            internal TrackedObject this[MetaAssociation assoc, TrackedObject from] { 
                get {
                    Dictionary pairs;
                    if (associations.TryGetValue(assoc, out pairs)) {
                        TrackedObject to; 
                        if (pairs.TryGetValue(from, out to)) {
                            return to; 
                        } 
                    }
                    return null; 
                }
            }
            internal void Clear() {
                this.associations.Clear(); 
            }
        } 
 
        class ReferenceMap {
            Dictionary> references; 

            internal ReferenceMap() {
                this.references = new Dictionary>();
            } 

            internal void Add(TrackedObject from, TrackedObject to) { 
                List refs; 
                if (!references.TryGetValue(from, out refs)) {
                    refs = new List(); 
                    references.Add(from, refs);
                }
                if (!refs.Contains(to))
                    refs.Add(to); 
            }
 
            internal IEnumerable this[TrackedObject from] { 
                get {
                    List refs; 
                    if (references.TryGetValue(from, out refs)) {
                        return refs;
                    }
                    return Empty; 
                }
            } 
 
            internal void Clear() {
                this.references.Clear(); 
            }

            private static TrackedObject[] Empty = new TrackedObject[] { };
        } 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
using System; 
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text; 

namespace System.Data.Linq { 
    using System.Data.Linq.Mapping; 
    using System.Data.Linq.Provider;
 
    /// 
    /// Describes the type of change the entity will undergo when submitted to the database.
    /// 
    public enum ChangeAction { 
        /// 
        /// The entity will not be submitted. 
        ///  
        None = 0,
        ///  
        /// The entity will be deleted.
        /// 
        Delete,
        ///  
        /// The entity will be inserted.
        ///  
        Insert, 
        /// 
        /// The entity will be updated. 
        /// 
        Update
    }
 
    internal class ChangeProcessor {
        CommonDataServices services; 
        DataContext context; 
        ChangeTracker tracker;
        ChangeDirector changeDirector; 
        EdgeMap currentParentEdges;
        EdgeMap originalChildEdges;
        ReferenceMap originalChildReferences;
 
        internal ChangeProcessor(CommonDataServices services, DataContext context) {
            this.services = services; 
            this.context = context; 
            this.tracker = services.ChangeTracker;
            this.changeDirector = services.ChangeDirector; 
            this.currentParentEdges = new EdgeMap();
            this.originalChildEdges = new EdgeMap();
            this.originalChildReferences = new ReferenceMap();
        } 

        internal void SubmitChanges(ConflictMode failureMode) { 
            this.TrackUntrackedObjects(); 
            // Must apply inferred deletions only after any untracked objects
            // are tracked 
            this.ApplyInferredDeletions();
            this.BuildEdgeMaps();

            var list = this.GetOrderedList(); 

            ValidateAll(list); 
 
            int numUpdatesAttempted = 0;
            ChangeConflictSession conflictSession = new ChangeConflictSession(this.context); 
            List conflicts = new List();
            List deletedItems = new List();
            List insertedItems = new List();
 
            foreach (TrackedObject item in list) {
                try { 
                    if (item.IsNew) { 
                        item.SynchDependentData();
                        changeDirector.Insert(item); 
                        // store all inserted items for post processing
                        insertedItems.Add(item);
                    }
                    else if (item.IsDeleted) { 
                        // Delete returns 1 if the delete was successfull, 0 if the row exists
                        // but wasn't deleted due to an OC conflict, or -1 if the row was 
                        // deleted by another context (no OC conflict in this case) 
                        numUpdatesAttempted++;
                        int ret = changeDirector.Delete(item); 
                        if (ret == 0) {
                            conflicts.Add(new ObjectChangeConflict(conflictSession, item, false));
                        }
                        else { 
                            // store all deleted items for post processing
                            deletedItems.Add(item); 
                        } 
                    }
                    else if (item.IsPossiblyModified) { 
                        item.SynchDependentData();
                        if (item.IsModified) {
                            CheckForInvalidChanges(item);
                            numUpdatesAttempted++; 
                            if (changeDirector.Update(item) <= 0) {
                                conflicts.Add(new ObjectChangeConflict(conflictSession, item)); 
                            } 
                        }
                    } 
                }
                catch (ChangeConflictException) {
                    conflicts.Add(new ObjectChangeConflict(conflictSession, item));
                } 
                if (conflicts.Count > 0 && failureMode == ConflictMode.FailOnFirstConflict) {
                    break; 
                } 
            }
 
            // if we have accumulated any failed updates, throw the exception now
            if (conflicts.Count > 0) {
                this.context.ChangeConflicts.Fill(conflicts);
                throw CreateChangeConflictException(numUpdatesAttempted, conflicts.Count); 
            }
 
            // Only after all updates have been sucessfully processed do we want to make 
            // post processing modifications to the objects and/or cache state.
            PostProcessUpdates(insertedItems, deletedItems); 
        }

        private void PostProcessUpdates(List insertedItems, List deletedItems) {
            // perform post delete processing 
            foreach (TrackedObject deletedItem in deletedItems) {
                // remove deleted item from identity cache 
                this.services.RemoveCachedObjectLike(deletedItem.Type, deletedItem.Original); 
                ClearForeignKeyReferences(deletedItem);
            } 

            // perform post insert processing
            foreach (TrackedObject insertedItem in insertedItems) {
                object lookup = this.services.InsertLookupCachedObject(insertedItem.Type, insertedItem.Current); 
                if (lookup != insertedItem.Current) {
                    throw new DuplicateKeyException(insertedItem.Current, Strings.DatabaseGeneratedAlreadyExistingKey); 
                } 
                insertedItem.InitializeDeferredLoaders();
            } 
        }

        /// 
        /// For each unidirectional and bi-directional 1:N and 1:1 association of a deleted 
        /// object, we perform the following reference cleanup:
        ///   - for 1:N we remove the deleted entity from the opposite EntitySet or collection 
        ///     and null out the back reference as well as the key fields 
        ///   - for 1:1 we null out the back reference and the key fields
        ///  
        private void ClearForeignKeyReferences(TrackedObject to) {
            foreach (MetaAssociation assoc in to.Type.Associations) {
                // If this is the 1-side of a bidirectional relationship and the relationship is with the parent's primary key,
                // we will try to fixup the relationship references on the objects 
                if (assoc.IsForeignKey && assoc.OtherMember != null && assoc.OtherMember.IsAssociation && assoc.OtherKeyIsPrimaryKey) {
                    // Search the cache for the target of the association, since 
                    // it might not be loaded on the object being deleted, and we 
                    // don't want to force a load.
                    object[] keyValues = CommonDataServices.GetForeignKeyValues(assoc, to.Current); 
                    object cached = this.services.IdentityManager.Find(assoc.OtherType, keyValues);

                    if (cached != null) {
                        if (assoc.OtherMember.Association.IsMany) { 
                            // Note that going through the IList interface handles
                            // EntitySet as well as POCO collections that implement IList 
                            // and are not FixedSize. 
                            System.Collections.IList collection = assoc.OtherMember.MemberAccessor.GetBoxedValue(cached) as System.Collections.IList;
                            if (collection != null && !collection.IsFixedSize) { 
                                collection.Remove(to.Current);
                                ClearForeignKeysHelper(assoc, to.Current);
                            }
                        } 
                        else {
                            // Null out the other association.  Since this is a 1:1 association, 
                            // we're not concerned here with causing a deferred load, since the 
                            // target is already cached (since we're deleting it).
                            assoc.OtherMember.MemberAccessor.SetBoxedValue(ref cached, null); 
                            ClearForeignKeysHelper(assoc, to.Current);
                        }
                    }
                } 
                else if (assoc.IsForeignKey) {
                    // If this is a unidirectional relationship or the relationship is with a unique column on the parent, and not a primary key, 
                    // then we just need to clear the relationship on our side 
                    ClearForeignKeysHelper(assoc, to.Current);
                } 
            }
        }

        private void ClearForeignKeysHelper(MetaAssociation assoc, object trackedInstance) { 
            assoc.ThisMember.MemberAccessor.SetBoxedValue(ref trackedInstance, null);
 
            for (int i = 0, n = assoc.ThisKey.Count; i < n; i++) { 
                MetaDataMember thisKey = assoc.ThisKey[i];
                if (thisKey.CanBeNull) { 
                    thisKey.StorageAccessor.SetBoxedValue(ref trackedInstance, null);
                }
            }
        } 

        private static void ValidateAll(IEnumerable list) { 
            foreach (var item in list) { 
                if (item.IsNew) {
                    item.SynchDependentData(); 
                    if (item.Type.HasAnyValidateMethod) {
                        SendOnValidate(item.Type, item, ChangeAction.Insert);
                    }
                } else if (item.IsDeleted) { 
                    if (item.Type.HasAnyValidateMethod) {
                        SendOnValidate(item.Type, item, ChangeAction.Delete); 
                    } 
                } else if (item.IsPossiblyModified) {
                    item.SynchDependentData(); 
                    if (item.IsModified && item.Type.HasAnyValidateMethod) {
                        SendOnValidate(item.Type, item, ChangeAction.Update);
                    }
                } 
            }
        } 
 
        private static void SendOnValidate(MetaType type, TrackedObject item, ChangeAction changeAction) {
            if (type != null) { 
                SendOnValidate(type.InheritanceBase, item, changeAction);

                if (type.OnValidateMethod != null) {
                    try { 
                        type.OnValidateMethod.Invoke(item.Current, new object[] { changeAction });
                    } catch (TargetInvocationException tie) { 
                        if (tie.InnerException != null) { 
                            throw tie.InnerException;
                        } 

                        throw;
                    }
                } 
            }
        } 
 
        internal string GetChangeText() {
            this.ObserveUntrackedObjects(); 
            // Must apply inferred deletions only after any untracked objects
            // are tracked
            this.ApplyInferredDeletions();
            this.BuildEdgeMaps(); 

            // append change text only 
            StringBuilder changeText = new StringBuilder(); 
            foreach (TrackedObject item in this.GetOrderedList()) {
                if (item.IsNew) { 
                    item.SynchDependentData();
                    changeDirector.AppendInsertText(item, changeText);
                }
                else if (item.IsDeleted) { 
                    changeDirector.AppendDeleteText(item, changeText);
                } 
                else if (item.IsPossiblyModified) { 
                    item.SynchDependentData();
                    if (item.IsModified) { 
                        changeDirector.AppendUpdateText(item, changeText);
                    }
                }
            } 
            return changeText.ToString();
        } 
 
        internal ChangeSet GetChangeSet() {
            List newEntities = new List(); 
            List deletedEntities = new List();
            List changedEntities = new List();

            this.ObserveUntrackedObjects(); 
            // Must apply inferred deletions only after any untracked objects
            // are tracked 
            this.ApplyInferredDeletions(); 

            foreach (TrackedObject item in this.tracker.GetInterestingObjects()) { 
                if (item.IsNew) {
                    item.SynchDependentData();
                    newEntities.Add(item.Current);
                } 
                else if (item.IsDeleted) {
                    deletedEntities.Add(item.Current); 
                } 
                else if (item.IsPossiblyModified) {
                    item.SynchDependentData(); 
                    if (item.IsModified) {
                        changedEntities.Add(item.Current);
                    }
                } 
            }
 
            return new ChangeSet(newEntities.AsReadOnly(), deletedEntities.AsReadOnly(), changedEntities.AsReadOnly()); 
        }
 
        // verify that primary key and db-generated values have not changed
        private static void CheckForInvalidChanges(TrackedObject tracked) {
            foreach (MetaDataMember mem in tracked.Type.PersistentDataMembers) {
                if (mem.IsPrimaryKey || mem.IsDbGenerated || mem.IsVersion) { 
                    if (tracked.HasChangedValue(mem)) {
                        if (mem.IsPrimaryKey) { 
                            throw Error.IdentityChangeNotAllowed(mem.Name, tracked.Type.Name); 
                        }
                        else { 
                            throw Error.DbGeneratedChangeNotAllowed(mem.Name, tracked.Type.Name);
                        }
                    }
                } 
            }
        } 
 
        /// 
        /// Create an ChangeConflictException with the best message 
        /// 
        static private ChangeConflictException CreateChangeConflictException(int totalUpdatesAttempted, int failedUpdates) {
            string msg = Strings.RowNotFoundOrChanged;
            if (totalUpdatesAttempted > 1) { 
                msg = Strings.UpdatesFailedMessage(failedUpdates, totalUpdatesAttempted);
            } 
            return new ChangeConflictException(msg); 
        }
 
        internal void TrackUntrackedObjects() {
            Dictionary visited = new Dictionary();

            // search for untracked new objects 
            List items = new List(this.tracker.GetInterestingObjects());
            foreach (TrackedObject item in items) { 
                this.TrackUntrackedObjects(item.Type, item.Current, visited); 
            }
        } 

        internal void ApplyInferredDeletions() {
            foreach (TrackedObject item in this.tracker.GetInterestingObjects()) {
                if (item.CanInferDelete()) { 
                    // based on DeleteOnNull specifications on the item's associations,
                    // a deletion can be inferred for this item.  The actual state transition 
                    // is dependent on the current item state. 
                    if (item.IsNew) {
                        item.ConvertToRemoved(); 
                    }
                    else if (item.IsPossiblyModified || item.IsModified) {
                        item.ConvertToDeleted();
                    } 
                }
            } 
        } 

        private void TrackUntrackedObjects(MetaType type, object item, Dictionary visited) { 
            if (!visited.ContainsKey(item)) {
                visited.Add(item, item);
                TrackedObject tracked = this.tracker.GetTrackedObject(item);
                if (tracked == null) { 
                    tracked = this.tracker.Track(item);
                    tracked.ConvertToNew(); 
                } 
                else if (tracked.IsDead || tracked.IsRemoved) {
                    // ignore 
                    return;
                }

                // search parents (objects we are dependent on) 
                foreach (RelatedItem parent in this.services.GetParents(type, item)) {
                    this.TrackUntrackedObjects(parent.Type, parent.Item, visited); 
                } 

                // synch up primary key 
                if (tracked.IsNew) {
                    tracked.InitializeDeferredLoaders();

                    if (!tracked.IsPendingGeneration(tracked.Type.IdentityMembers)) { 
                        tracked.SynchDependentData();
                        object cached = this.services.InsertLookupCachedObject(tracked.Type, item); 
                        if (cached != item) { 
                            TrackedObject cachedTracked = this.tracker.GetTrackedObject(cached);
                            System.Diagnostics.Debug.Assert(cachedTracked != null); 
                            if (cachedTracked.IsDeleted || cachedTracked.CanInferDelete()) {
                                // adding new object with same ID as object being deleted.. turn into modified
                                tracked.ConvertToPossiblyModified(cachedTracked.Original);
                                // turn deleted to dead... 
                                cachedTracked.ConvertToDead();
 
                                this.services.RemoveCachedObjectLike(tracked.Type, item); 
                                this.services.InsertLookupCachedObject(tracked.Type, item);
                            } 
                            else if (!cachedTracked.IsDead) {
                                throw new DuplicateKeyException(item, Strings.CantAddAlreadyExistingKey);
                            }
                        } 
                    }
                    else { 
                        // we may have a generated PK, however we set the PK on this new item to 
                        // match a deleted item
                        object cached = this.services.GetCachedObjectLike(tracked.Type, item); 
                        if (cached != null) {
                            TrackedObject cachedTracked = this.tracker.GetTrackedObject(cached);
                            System.Diagnostics.Debug.Assert(cachedTracked != null);
                            if (cachedTracked.IsDeleted || cachedTracked.CanInferDelete()) { 
                                // adding new object with same ID as object being deleted.. turn into modified
                                tracked.ConvertToPossiblyModified(cachedTracked.Original); 
                                // turn deleted to dead... 
                                cachedTracked.ConvertToDead();
 
                                this.services.RemoveCachedObjectLike(tracked.Type, item);
                                this.services.InsertLookupCachedObject(tracked.Type, item);
                            }
                        } 
                    }
                } 
 
                // search children (objects that are dependent on us)
                foreach (RelatedItem child in this.services.GetChildren(type, item)) { 
                    this.TrackUntrackedObjects(child.Type, child.Item, visited);
                }
            }
        } 

        internal void ObserveUntrackedObjects() { 
            Dictionary visited = new Dictionary(); 

            List items = new List(this.tracker.GetInterestingObjects()); 
            foreach (TrackedObject item in items) {
                this.ObserveUntrackedObjects(item.Type, item.Current, visited);
            }
        } 

        private void ObserveUntrackedObjects(MetaType type, object item, Dictionary visited) { 
            if (!visited.ContainsKey(item)) { 
                visited.Add(item, item);
                TrackedObject tracked = this.tracker.GetTrackedObject(item); 
                if (tracked == null) {
                    tracked = this.tracker.Track(item);
                    tracked.ConvertToNew();
                } else if (tracked.IsDead || tracked.IsRemoved) { 
                    // ignore
                    return; 
                } 

                // search parents (objects we are dependent on) 
                foreach (RelatedItem parent in this.services.GetParents(type, item)) {
                    this.ObserveUntrackedObjects(parent.Type, parent.Item, visited);
                }
 
                // synch up primary key unless its generated.
                if (tracked.IsNew) { 
                     if (!tracked.IsPendingGeneration(tracked.Type.IdentityMembers)) { 
                        tracked.SynchDependentData();
                     } 
                }

                // search children (objects that are dependent on us)
                foreach (RelatedItem child in this.services.GetChildren(type, item)) { 
                    this.ObserveUntrackedObjects(child.Type, child.Item, visited);
                } 
            } 
        }
 
        private TrackedObject GetOtherItem(MetaAssociation assoc, object instance) {
            if (instance == null)
                return null;
            object other = null; 
            // Don't load unloaded references
            if (assoc.ThisMember.StorageAccessor.HasAssignedValue(instance) || 
                assoc.ThisMember.StorageAccessor.HasLoadedValue(instance) 
                ) {
                other = assoc.ThisMember.MemberAccessor.GetBoxedValue(instance); 
            }
            else if (assoc.OtherKeyIsPrimaryKey) {
                // Maybe it's in the cache, but not yet attached through reference.
                object[] foreignKeys = CommonDataServices.GetForeignKeyValues(assoc, instance); 
                other = this.services.GetCachedObject(assoc.OtherType, foreignKeys);
            } 
            // else the other key is not the primary key so there is no way to try to look it up 
            return (other != null) ? this.tracker.GetTrackedObject(other) : null;
        } 

        private bool HasAssociationChanged(MetaAssociation assoc, TrackedObject item) {
            if (item.Original != null && item.Current != null) {
                if (assoc.ThisMember.StorageAccessor.HasAssignedValue(item.Current) || 
                    assoc.ThisMember.StorageAccessor.HasLoadedValue(item.Current)
                    ) { 
                    return this.GetOtherItem(assoc, item.Current) != this.GetOtherItem(assoc, item.Original); 
                }
                else { 
                    object[] currentFKs = CommonDataServices.GetForeignKeyValues(assoc, item.Current);
                    object[] originaFKs = CommonDataServices.GetForeignKeyValues(assoc, item.Original);
                    for (int i = 0, n = currentFKs.Length; i < n; i++) {
                        if (!object.Equals(currentFKs[i], originaFKs[i])) 
                            return true;
                    } 
                } 
            }
            return false; 
        }

        private void BuildEdgeMaps() {
            this.currentParentEdges.Clear(); 
            this.originalChildEdges.Clear();
            this.originalChildReferences.Clear(); 
 
            List list = new List(this.tracker.GetInterestingObjects());
            foreach (TrackedObject item in list) { 
                bool isNew = item.IsNew;
                MetaType mt = item.Type;
                foreach (MetaAssociation assoc in mt.Associations) {
                    if (assoc.IsForeignKey) { 
                        TrackedObject otherItem = this.GetOtherItem(assoc, item.Current);
                        TrackedObject dbOtherItem = this.GetOtherItem(assoc, item.Original); 
                        bool pointsToDeleted = (otherItem != null && otherItem.IsDeleted) || (dbOtherItem != null && dbOtherItem.IsDeleted); 
                        bool pointsToNew = (otherItem != null && otherItem.IsNew);
 
                        if (isNew || pointsToDeleted || pointsToNew || this.HasAssociationChanged(assoc, item)) {
                            if (otherItem != null) {
                                this.currentParentEdges.Add(assoc, item, otherItem);
                            } 
                            if (dbOtherItem != null) {
                                if (assoc.IsUnique) { 
                                    this.originalChildEdges.Add(assoc, dbOtherItem, item); 
                                }
                                this.originalChildReferences.Add(dbOtherItem, item); 
                            }
                        }
                    }
                } 
            }
        } 
 
        enum VisitState {
            Before, 
            After
        }

        private List GetOrderedList() { 
            var objects = this.tracker.GetInterestingObjects().ToList();
 
            // give list an initial order (most likely correct order) to avoid deadlocks in server 
            var range = Enumerable.Range(0, objects.Count).ToList();
            range.Sort((int x, int y) => Compare(objects[x], x, objects[y], y)); 
            var ordered = range.Select(i => objects[i]).ToList();

            // permute order if constraint dependencies requires some changes to come before others
            var visited = new Dictionary(); 
            var list = new List();
            foreach (TrackedObject item in ordered) { 
                this.BuildDependencyOrderedList(item, list, visited); 
            }
            return list; 
        }

        private static int Compare(TrackedObject x, int xOrdinal, TrackedObject y, int yOrdinal) {
            // deal with possible nulls 
            if (x == y) {
                return 0; 
            } 
            if (x == null) {
                return -1; 
            }
            else if (y == null) {
                return 1;
            } 
            // first order by action: Inserts first, Updates, Deletes last
            int xAction = x.IsNew ? 0 : x.IsDeleted ? 2 : 1; 
            int yAction = y.IsNew ? 0 : y.IsDeleted ? 2 : 1; 
            if (xAction < yAction) {
                return -1; 
            }
            else if (xAction > yAction) {
                return 1;
            } 
            // no need to order inserts (PK's may not even exist)
            if (x.IsNew) { 
                // keep original order 
                return xOrdinal.CompareTo(yOrdinal);
            } 
            // second order by type
            if (x.Type != y.Type) {
                return string.CompareOrdinal(x.Type.Type.FullName, y.Type.Type.FullName);
            } 
            // lastly, order by PK values
            int result = 0; 
            foreach (MetaDataMember mm in x.Type.IdentityMembers) { 
                object xValue = mm.StorageAccessor.GetBoxedValue(x.Current);
                object yValue = mm.StorageAccessor.GetBoxedValue(y.Current); 
                if (xValue == null) {
                    if (yValue != null) {
                        return -1;
                    } 
                }
                else { 
                    IComparable xc = xValue as IComparable; 
                    if (xc != null) {
                        result = xc.CompareTo(yValue); 
                        if (result != 0) {
                            return result;
                        }
                    } 
                }
            } 
            // they are the same? leave in original order 
            return xOrdinal.CompareTo(yOrdinal);
        } 

        private void BuildDependencyOrderedList(TrackedObject item, List list, Dictionary visited) {
            VisitState state;
            if (visited.TryGetValue(item, out state)) { 
                if (state == VisitState.Before) {
                    throw Error.CycleDetected(); 
                } 
                return;
            } 

            visited[item] = VisitState.Before;

            if (item.IsInteresting) { 
                if (item.IsDeleted) {
                    // if 'item' is deleted 
                    //    all objects that used to refer to 'item' must be ordered before item 
                    foreach (TrackedObject other in this.originalChildReferences[item]) {
                        if (other != item) { 
                            this.BuildDependencyOrderedList(other, list, visited);
                        }
                    }
                } 
                else {
                    // if 'item' is new or changed 
                    //   for all objects 'other' that 'item' refers to along association 'assoc' 
                    //      if 'other' is new then 'other' must be ordered before 'item'
                    //      if 'assoc' is pure one-to-one and some other item 'prevItem' used to refer to 'other' 
                    //         then 'prevItem' must be ordered before 'item'
                    foreach (MetaAssociation assoc in item.Type.Associations) {
                        if (assoc.IsForeignKey) {
                            TrackedObject other = this.currentParentEdges[assoc, item]; 
                            if (other != null) {
                                if (other.IsNew) { 
                                    // if other is new, visit other first (since item's FK depends on it) 
                                    if (other != item || item.Type.DBGeneratedIdentityMember != null) {
                                        this.BuildDependencyOrderedList(other, list, visited); 
                                    }
                                }
                                else if ((assoc.IsUnique || assoc.ThisKeyIsPrimaryKey)) {
                                    TrackedObject prevItem = this.originalChildEdges[assoc, other]; 
                                    if (prevItem != null && other != item) {
                                        this.BuildDependencyOrderedList(prevItem, list, visited); 
                                    } 
                                }
                            } 
                        }
                    }
                }
 
                list.Add(item);
            } 
 
            visited[item] = VisitState.After;
        } 

        class EdgeMap {
            Dictionary> associations;
 
            internal EdgeMap() {
                this.associations = new Dictionary>(); 
            } 

            internal void Add(MetaAssociation assoc, TrackedObject from, TrackedObject to) { 
                Dictionary pairs;
                if (!associations.TryGetValue(assoc, out pairs)) {
                    pairs = new Dictionary();
                    associations.Add(assoc, pairs); 
                }
                pairs.Add(from, to); 
            } 

            internal TrackedObject this[MetaAssociation assoc, TrackedObject from] { 
                get {
                    Dictionary pairs;
                    if (associations.TryGetValue(assoc, out pairs)) {
                        TrackedObject to; 
                        if (pairs.TryGetValue(from, out to)) {
                            return to; 
                        } 
                    }
                    return null; 
                }
            }
            internal void Clear() {
                this.associations.Clear(); 
            }
        } 
 
        class ReferenceMap {
            Dictionary> references; 

            internal ReferenceMap() {
                this.references = new Dictionary>();
            } 

            internal void Add(TrackedObject from, TrackedObject to) { 
                List refs; 
                if (!references.TryGetValue(from, out refs)) {
                    refs = new List(); 
                    references.Add(from, refs);
                }
                if (!refs.Contains(to))
                    refs.Add(to); 
            }
 
            internal IEnumerable this[TrackedObject from] { 
                get {
                    List refs; 
                    if (references.TryGetValue(from, out refs)) {
                        return refs;
                    }
                    return Empty; 
                }
            } 
 
            internal void Clear() {
                this.references.Clear(); 
            }

            private static TrackedObject[] Empty = new TrackedObject[] { };
        } 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
                        

                        

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