ChangeProcessor.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / 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 (assoc.IsForeignKey && assoc.OtherMember != null && assoc.OtherMember.IsAssociation) {
                    // 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) {
                    System.Diagnostics.Debug.Assert(assoc.OtherMember == null); 
                    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;
            // Don't load unloaded references
            if (assoc.ThisMember.StorageAccessor.HasAssignedValue(instance) ||
                assoc.ThisMember.StorageAccessor.HasLoadedValue(instance) 
                ) {
                other = assoc.ThisMember.MemberAccessor.GetBoxedValue(instance); 
            } 
            else {
                // 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);
            }
            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 (assoc.IsForeignKey && assoc.OtherMember != null && assoc.OtherMember.IsAssociation) {
                    // 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) {
                    System.Diagnostics.Debug.Assert(assoc.OtherMember == null); 
                    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;
            // Don't load unloaded references
            if (assoc.ThisMember.StorageAccessor.HasAssignedValue(instance) ||
                assoc.ThisMember.StorageAccessor.HasLoadedValue(instance) 
                ) {
                other = assoc.ThisMember.MemberAccessor.GetBoxedValue(instance); 
            } 
            else {
                // 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);
            }
            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