[WorkItem(191649)] // related bug
        public void Composition_MoveChildToNewParent()
        {
            ConfigurableEntityContainer container = new ConfigurableEntityContainer();
            container.CreateSet<Parent>(EntitySetOperations.All);
            container.CreateSet<Child>(EntitySetOperations.All);
            container.CreateSet<GrandChild>(EntitySetOperations.All);
            container.CreateSet<GreatGrandChild>(EntitySetOperations.All);
            EntitySet<Parent> parentSet = container.GetEntitySet<Parent>();
            EntitySet<Child> childSet = container.GetEntitySet<Child>();
            EntitySet<GreatGrandChild> ggChildSet = container.GetEntitySet<GreatGrandChild>();

            Parent p1 = new Parent() { ID = 1 };
            Parent p2 = new Parent() { ID = 2 };
            container.LoadEntities(new Entity[] { p1, new Child { ID = 1, ParentID = 1 }, new Child { ID = 2, ParentID = 1 }, 
                                                  p2, new Child { ID = 3, ParentID = 2 }, new Child { ID = 4, ParentID = 2 } });

            Assert.AreEqual(2, p1.Children.Count());
            Assert.AreEqual(2, p2.Children.Count());

            Child child = p1.Children.ElementAt(0);
            p1.Children.Remove(child);

            Assert.AreEqual(1, p1.Children.Count());
            Assert.AreEqual(2, p2.Children.Count());

            // This is the repro - this addition was causing the counts to be 1 and 4 below
            p2.Children.Add(child);

            Assert.AreEqual(1, p1.Children.Count);
            Assert.AreEqual(3, p2.Children.Count);

            Assert.AreEqual(EntityState.Modified, child.EntityState);
            Assert.AreSame(p2, child.Parent);
        }
        public void DeleteParent(Parent parent)
        {
            CompositionHelper.Validate(parent);
            SetOperationInvoked(parent);

            ((CompositionEntityBase)parent).OperationResult = "Delete";
        }
        public void UpdateParent(Parent parent)
        {
            // first validate the hierarchy
            CompositionHelper.Validate(parent);

            ((CompositionEntityBase)parent).OperationResult = "Update";

            // for an updated, children might be added, updated
            // or deleted. We need to enumerate all and act appropriately
            foreach (Child child in this.ChangeSet.GetAssociatedChanges(parent, p => p.Children))
            {
                ChangeOperation changeOp = this.ChangeSet.GetChangeOperation(child);
                if (changeOp == ChangeOperation.Insert)
                {
                    ((CompositionEntityBase)child).OperationResult = "Insert";
                }
                else if (changeOp == ChangeOperation.Update)
                {
                    ((CompositionEntityBase)child).OperationResult = "Update";
                }
                else if (changeOp == ChangeOperation.Delete)
                {
                    ((CompositionEntityBase)child).OperationResult = "Delete";
                }

                // now process child updates for the child
                foreach (GrandChild grandChild in this.ChangeSet.GetAssociatedChanges(child, p => p.Children))
                {
                    changeOp = this.ChangeSet.GetChangeOperation(grandChild);
                    if (changeOp == ChangeOperation.Insert)
                    {
                        ((CompositionEntityBase)grandChild).OperationResult = "Insert";
                    }
                    else if (changeOp == ChangeOperation.Update)
                    {
                        ((CompositionEntityBase)grandChild).OperationResult = "Update";
                    }
                    else if (changeOp == ChangeOperation.Delete)
                    {
                        ((CompositionEntityBase)grandChild).OperationResult = "Delete";
                    }

                    // finally, process any great grand child updates
                    GreatGrandChild updatedGreateGrandChild = this.ChangeSet.GetAssociatedChanges(grandChild, p => p.Child).Cast<GreatGrandChild>().SingleOrDefault();
                    if (updatedGreateGrandChild != null)
                    {
                        changeOp = this.ChangeSet.GetChangeOperation(updatedGreateGrandChild);
                        if (changeOp == ChangeOperation.Insert)
                        {
                            ((CompositionEntityBase)updatedGreateGrandChild).OperationResult = "Insert";
                        }
                        else if (changeOp == ChangeOperation.Update)
                        {
                            ((CompositionEntityBase)updatedGreateGrandChild).OperationResult = "Update";
                        }
                        else if (changeOp == ChangeOperation.Delete)
                        {
                            ((CompositionEntityBase)updatedGreateGrandChild).OperationResult = "Delete";
                        }
                    }
                }
            }
        }
        public void InsertParent(Parent parent)
        {
            // first validate the hierarchy
            CompositionHelper.Validate(parent);

            ((CompositionEntityBase)parent).OperationResult = "Insert";

            // for a new parent, all children will also be new
            // so we enumerate all and "add" them
            foreach (Child child in parent.Children)
            {
                ((CompositionEntityBase)child).OperationResult = "Insert";
                foreach (GrandChild grandChild in child.Children)
                {
                    ((CompositionEntityBase)grandChild).OperationResult = "Insert";
                    GreatGrandChild greatGrandChild = grandChild.Child;
                    if (greatGrandChild != null)
                    {
                        ((CompositionEntityBase)greatGrandChild).OperationResult = "Insert";
                    }
                }
            }
        }
        /// <summary>
        /// Use the changeset composition APIs to navigate all child updates
        /// </summary>
        /// <param name="parent"></param>
        private void NavigateChildChanges(Parent parent)
        {
            // if the parent has had property modifications, original will
            // be non-null
            if (this.ChangeSet.GetChangeOperation(parent) != ChangeOperation.Insert)
            {
                Parent originalParent = this.ChangeSet.GetOriginal(parent);
            }

            // navigate all child changes w/o specifying operation type
            Dictionary<object, ChangeOperation> changeOperationMap = new Dictionary<object, ChangeOperation>();
            foreach (Child child in this.ChangeSet.GetAssociatedChanges(parent, p => p.Children))
            {
                ChangeOperation op = this.ChangeSet.GetChangeOperation(child);             
                changeOperationMap[child] = op;

                if (this.ChangeSet.GetChangeOperation(child) != ChangeOperation.Insert)
                {
                    Child originalChild = this.ChangeSet.GetOriginal(child);
                }

                if (op == ChangeOperation.None)
                {

                }
                else if (op == ChangeOperation.Insert)
                {
                    CompositionHelper.Assert(this.ChangeSet.ChangeSetEntries.SingleOrDefault(p => p.Entity == child && p.Operation == DomainOperation.Insert) != null,
                    "Expected corresponding insert operation not found.");
                }
                else if (op == ChangeOperation.Update)
                {
                    CompositionHelper.Assert(this.ChangeSet.ChangeSetEntries.SingleOrDefault(p => p.Entity == child && p.Operation == DomainOperation.Update) != null,
                    "Expected corresponding update operation not found.");
                }
                else if (op == ChangeOperation.Delete)
                {
                    CompositionHelper.Assert(this.ChangeSet.ChangeSetEntries.SingleOrDefault(p => p.Entity == child && p.Operation == DomainOperation.Delete) != null,
                    "Expected corresponding delete operation not found.");
                }

                foreach (GrandChild grandChild in this.ChangeSet.GetAssociatedChanges(child, p => p.Children))
                {
                    changeOperationMap[grandChild] = this.ChangeSet.GetChangeOperation(grandChild);
                    foreach (GreatGrandChild greatGrandChild in this.ChangeSet.GetAssociatedChanges(grandChild, p => p.Child))
                    {
                        changeOperationMap[greatGrandChild] = this.ChangeSet.GetChangeOperation(greatGrandChild);
                    }
                }
            }

            // verify all child operations against the map we built up during enumeration
            // of associated changes to ensure all operations were returned
            foreach (ChangeSetEntry operation in this.ChangeSet.ChangeSetEntries.Where(p => p.Entity.GetType() != typeof(Parent)))
            {
                switch (operation.Operation)
                {
                    case DomainOperation.Insert:
                        CompositionHelper.Assert(changeOperationMap.ContainsKey(operation.Entity) && 
                            changeOperationMap[operation.Entity] == ChangeOperation.Insert,
                            "Expected insert operation was not returned from GetAssociatedChanges.");
                        break;
                    case DomainOperation.Update:
                        CompositionHelper.Assert(changeOperationMap.ContainsKey(operation.Entity) &&
                            changeOperationMap[operation.Entity] == ChangeOperation.Update,
                            "Expected update operation was not returned from GetAssociatedChanges.");
                        break;
                    case DomainOperation.Delete:
                        CompositionHelper.Assert(
                            changeOperationMap.ContainsKey(operation.Entity) &&
                            changeOperationMap[operation.Entity] == ChangeOperation.Delete,
                            "Expected delete operation was not returned from GetAssociatedChanges.");
                        break;
                }
            }

            // navigate all child changes specifying operation type
            foreach (Child child in this.ChangeSet.GetAssociatedChanges(parent, p => p.Children, ChangeOperation.None))
            {
            }
            foreach (Child child in this.ChangeSet.GetAssociatedChanges(parent, p => p.Children, ChangeOperation.Insert))
            {
            }
            foreach (Child child in this.ChangeSet.GetAssociatedChanges(parent, p => p.Children, ChangeOperation.Update))
            {
            }
            foreach (Child child in this.ChangeSet.GetAssociatedChanges(parent, p => p.Children, ChangeOperation.Delete))
            {
            }
        }
        public void Composition_AddChildPreviouslyReferencingOtherParent()
        {
            ConfigurableEntityContainer container = new ConfigurableEntityContainer();
            container.CreateSet<Parent>(EntitySetOperations.All);
            container.CreateSet<Child>(EntitySetOperations.All);
            container.CreateSet<GrandChild>(EntitySetOperations.All);
            container.CreateSet<GreatGrandChild>(EntitySetOperations.All);
            EntitySet<Parent> parentSet = container.GetEntitySet<Parent>();
            EntitySet<Child> childSet = container.GetEntitySet<Child>();
            EntitySet<GreatGrandChild> ggChildSet = container.GetEntitySet<GreatGrandChild>();

            Parent p1 = new Parent() { ID = 1 };
            Parent p2 = new Parent() { ID = 2 };
            container.LoadEntities(new Entity[] { p1, p2 });

            Child child = new Child() { ID = 1, ParentID = 2 };
            p1.Children.Add(child);

            Assert.AreEqual(EntityState.Modified, p1.EntityState);
            Assert.IsTrue(childSet.HasChanges);
        }
 public static void Validate(Parent parent)
 {
     // Simulate a validation pass that enumerates all children.
     foreach (Child child in parent.Children)
     {
         foreach (GrandChild grandChild in child.Children)
         {
             GreatGrandChild greatGrandChild = grandChild.Child;
         }
     }
 }
 public void CustomOp_Parent(Parent parent)
 {
     CompositionHelper.Validate(parent);
     this.SetOperationInvoked(parent, "CustomOp_Parent");
     ((CompositionEntityBase)parent).OperationResult += ",CustomOp_Parent";
 }
        public void Composition_ParentUpdateOnDelete()
        {
            ConfigurableEntityContainer container = new ConfigurableEntityContainer();
            container.CreateSet<Parent>(EntitySetOperations.All);
            container.CreateSet<Child>(EntitySetOperations.All);
            container.CreateSet<GrandChild>(EntitySetOperations.All);
            EntitySet<Parent> parentSet = container.GetEntitySet<Parent>();
            EntitySet<Child> childSet = container.GetEntitySet<Child>();

            Parent parent1 = new Parent() { ID = 1 };
            Child child1 = new Child() { ID = 1, Parent = parent1 };
            container.LoadEntities(new Entity[] { parent1, child1 });

            // insert new parent and child
            Parent newParent = new Parent();
            Child newChild = new Child();
            newChild.Parent = newParent;
            parentSet.Add(newParent);

            // When the parent is removed, all it's children are also removed.
            // This causes the child ParentID to be set to 0, which means it now
            // matches the new Parent.Children collection. The removed child is added to
            // the new parent's collection, which causes child.Parent to be updated.
            // However that add is transitory - once the child is removed from the
            // entity set, it is removed from the new entity's child collection.
            // That invalid update will result in an exception on submit.
            parentSet.Remove(parent1);
            Assert.AreEqual(1, newParent.Children.Count);
            Assert.IsTrue(newParent.Children.Contains(newChild));

            // if we allow the transitory phase, on accept changes we have to be
            // sure things are fixed up
            Assert.AreEqual(EntityState.Deleted, parent1.EntityState);
            Assert.AreEqual(EntityState.Deleted, child1.EntityState);
            Assert.AreSame(newParent, newChild.Parent);

            // changeset validation succeeds, since the parent is deleted along
            // with the child
            EntityChangeSet changeSet = container.GetChanges();
            Assert.IsTrue(changeSet.AddedEntities.Count == 2 && changeSet.RemovedEntities.Count == 2);
            ChangeSetBuilder.CheckForInvalidUpdates(changeSet);
        }
        public void Composition_InvalidParentUpdate_EntityCollection()
        {
            ConfigurableEntityContainer container = new ConfigurableEntityContainer();
            container.CreateSet<Parent>(EntitySetOperations.All);
            container.CreateSet<Child>(EntitySetOperations.All);
            container.CreateSet<GrandChild>(EntitySetOperations.All);
            EntitySet<Parent> parentSet = container.GetEntitySet<Parent>();
            EntitySet<Child> childSet = container.GetEntitySet<Child>();

            Parent parent1 = new Parent() { ID = 1 };
            Parent parent2 = new Parent() { ID = 2 };
            Child child = new Child() { ID = 1, Parent = parent1 };

            parentSet.Attach(parent1);
            parentSet.Attach(parent2);

            Assert.IsTrue(childSet.IsAttached(child), "Child was not attached automatically.");
            Assert.AreSame(parent1, ((Entity)child).Parent, "Entity.Parent doesn't reflect the parent-child relationship.");
            
            // point the child to a new parent, which results
            // in the child being reparented (an invalid update)
            child.Parent = parent2;

            EntityChangeSet changeSet = container.GetChanges();
            Assert.AreEqual(EntityState.Modified, child.EntityState);
            Assert.AreEqual(EntityState.Modified, parent1.EntityState);
            Assert.AreEqual(EntityState.Modified, parent2.EntityState);

            // Verify that changing the parent throws an exception when
            // changes are validated
            ExceptionHelper.ExpectInvalidOperationException(delegate
            {
                ChangeSetBuilder.CheckForInvalidUpdates(changeSet);
            }, string.Format(Resource.Entity_CantReparentComposedChild, child));
        }
        public void Composition_MultipleRemoveReadds()
        {
            ConfigurableEntityContainer container = new ConfigurableEntityContainer();
            container.CreateSet<Parent>(EntitySetOperations.All);
            container.CreateSet<Child>(EntitySetOperations.All);
            container.CreateSet<GrandChild>(EntitySetOperations.All);
            container.CreateSet<GreatGrandChild>(EntitySetOperations.All);
            EntitySet<Parent> parentSet = container.GetEntitySet<Parent>();
            EntitySet<Child> childSet = container.GetEntitySet<Child>();
            EntitySet<GreatGrandChild> ggChildSet = container.GetEntitySet<GreatGrandChild>();

            Parent parent = new Parent() { ID = 1 };
            Child child = new Child() { ID = 1, Parent = parent };
            container.LoadEntities(new Entity[] { parent, child });

            parent.Children.Remove(child);
            Assert.AreEqual(EntityState.Deleted, child.EntityState);
            Assert.IsFalse(childSet.Contains(child));

            parent.Children.Add(child);
            Assert.AreEqual(EntityState.Modified, child.EntityState);
            Assert.IsTrue(childSet.Contains(child));

            parent.Children.Remove(child);
            Assert.AreEqual(EntityState.Deleted, child.EntityState);
            Assert.IsFalse(childSet.Contains(child));

            // verify the same scenario for a singleton composition
            GrandChild gc = new GrandChild { ID = 1 };
            GreatGrandChild ggc = new GreatGrandChild { ID = 1, ParentID = 1 };
            container.LoadEntities(new Entity[] { gc, ggc });

            Assert.AreSame(ggc, gc.Child);

            gc.Child = null;
            Assert.AreEqual(EntityState.Deleted, ggc.EntityState);
            Assert.IsFalse(ggChildSet.Contains(ggc));

            gc.Child = ggc;
            Assert.AreEqual(EntityState.Unmodified, ggc.EntityState);
            Assert.IsTrue(ggChildSet.Contains(ggc));

            gc.Child = null;
            Assert.AreEqual(EntityState.Deleted, ggc.EntityState);
            Assert.IsFalse(ggChildSet.Contains(ggc));
        }
        /// <summary>
        /// Verify that each node in the hierarchy has the expected children
        /// and all Parent back pointers are valid.
        /// </summary>
        private static void VerifyHierarchy(Parent parent)
        {
            Assert.IsTrue(parent.Children.Count > 0);
            foreach (Child c in parent.Children)
            {
                Assert.AreSame(parent, c.Parent);

                Assert.IsTrue(c.Children.Count > 0);
                foreach (GrandChild gc in c.Children)
                {
                    Assert.AreSame(c, gc.Parent);
                    if (gc.Child != null)
                    {
                        Assert.AreSame(gc, gc.Child.Parent);
                    }
                }
            }
        }
        private static Parent CreateCompositionHierarchy(int numChildren, int numGrandChildren)
        {
            int parentKey = 1;
            int childKey = 1;
            int grandChildKey = 1;
            int greatGrandChildKey = 1;

            Parent p = new Parent
            {
                ID = parentKey++
            };

            for (int j = 0; j < numChildren; j++)
            {
                Child c = new Child
                {
                    ID = childKey++, ParentID = p.ID, Parent = p
                };
                p.Children.Add(c);
                for (int k = 0; k < numGrandChildren; k++)
                {
                    GrandChild gc = new GrandChild
                    {
                        ID = grandChildKey++, ParentID = c.ID, Parent = c
                    };
                    c.Children.Add(gc);

                    // add singleton child to grand child
                    gc.Child = new GreatGrandChild
                    {
                        ID = greatGrandChildKey++, ParentID = gc.ID, Parent = gc
                    };
                }
            }

            VerifyHierarchy(p);

            return p;
        }
        public void DeleteParent(Parent parent)
        {
            // normally you wouldn't validate a deleted hierarchy,
            // but we do it here for test validation
            CompositionHelper.Validate(parent);

            ((CompositionEntityBase)parent).OperationResult = "Delete";

            // Delete all children in the hierarchy.
            foreach (Child child in parent.Children)
            {
                foreach (GrandChild grandChild in child.Children)
                {
                    GreatGrandChild greatGrandChild = grandChild.Child;
                    if (greatGrandChild != null)
                    {
                    }
                }
            }
        }
        public void Entity_RemoveParent()
        {
            ConfigurableEntityContainer container = new ConfigurableEntityContainer();
            container.CreateSet<Parent>(EntitySetOperations.All);
            container.CreateSet<Child>(EntitySetOperations.All);
            container.CreateSet<GrandChild>(EntitySetOperations.All);
            EntitySet<Parent> parentSet = container.GetEntitySet<Parent>();
            EntitySet<Child> childSet = container.GetEntitySet<Child>();

            Parent parent1 = new Parent() { ID = 1 };
            Parent parent2 = new Parent() { ID = 2 };
            Child child = new Child() { ID = 1, Parent = parent1 };

            parentSet.Attach(parent1);
            parentSet.Attach(parent2);

            Assert.IsTrue(childSet.IsAttached(child), "Child was not attached automatically.");
            Assert.AreSame(parent1, ((Entity)child).Parent, "Entity.Parent doesn't reflect the parent-child relationship.");

            parentSet.Remove(parent1);
            EntityChangeSet changeSet = container.GetChanges();
            Assert.AreEqual(2, changeSet.Count());
        }
        public void CustomOp_Parent(Parent parent)
        {
            // first validate the hierarchy
            CompositionHelper.Validate(parent);

            ((CompositionEntityBase)parent).OperationResult += ",CustomOp_Parent";
        }
 private bool FilterParent(Parent entity)
 {
     return (entity.ID == this.ParentID);
 }
 public void CustomParentUpdate(Parent parent)
 {
 }
 /// <summary>
 /// Invokes the 'CustomOp_Parent' method of the specified <see cref="Parent"/> entity.
 /// </summary>
 /// <param name="parent">The <see cref="Parent"/> entity instance.</param>
 public void CustomOp_Parent(Parent parent)
 {
     parent.CustomOp_Parent();
 }
        /// <summary>
        /// Create a 4 level compositional hierarchy that includes both
        /// collection and singleton compositions
        /// </summary>
        public static List<Parent> CreateCompositionHierarchy()
        {
            int parentKey = 1;
            int childKey = 1;
            int grandChildKey = 1;
            int greatGrandChildKey = 1;

            List<Parent> parents = new List<Parent>();
            for (int i = 0; i < 3; i++)
            {
                Parent p = new Parent
                {
                    ID = parentKey++
                };
                parents.Add(p);
                for (int j = 0; j < 3; j++)
                {
                    Child c = new Child
                    {
                        ID = childKey++, ParentID = p.ID, Parent = p
                    };
                    p.Children.Add(c);
                    for (int k = 0; k < 3; k++)
                    {
                        GrandChild gc = new GrandChild
                        {
                            ID = grandChildKey++, ParentID = c.ID, Parent = c
                        };
                        c.Children.Add(gc);

                        // add singleton child to grand child
                        gc.Child = new GreatGrandChild
                        {
                            ID = greatGrandChildKey++, ParentID = gc.ID, Parent = gc
                        };
                    }
                }
            }

            return parents;
        }
        public void Changeset_GetAssociatedChanges_Enumerable()
        {
            DomainServiceDescription dsd = DomainServiceDescription.GetDescription(typeof(CompositionScenarios_Explicit));

            #region build up a compositional hierarchy
            int id = 1;
            Parent currParent = new Parent();
            List<ChangeSetEntry> changeSetEntries = new List<ChangeSetEntry>();
            ChangeSetEntry parentUpdateOperation = new ChangeSetEntry(id++, currParent, null, DomainOperation.Update);
            List<int> currentAssociatedChildren = new List<int>();
            changeSetEntries.Add(parentUpdateOperation);

            // add 3 unmodified children
            List<Child> unmodifiedChildren = new List<Child>() { new Child(), new Child(), new Child() };
            foreach (Child c in unmodifiedChildren)
            {
                ChangeSetEntry operation = new ChangeSetEntry(id++, c, c, DomainOperation.None);
                changeSetEntries.Add(operation);
                currParent.Children.Add(c);
                currentAssociatedChildren.Add(operation.Id);
            }

            // add 2 child edits
            List<Child> originalEditedChildren = new List<Child> { new Child(), new Child() };
            List<Child> currentEditedChildren = new List<Child> { new Child(), new Child() };
            for (int i = 0; i < originalEditedChildren.Count; i++)
            {
                Child currChild = currentEditedChildren[i];
                Child origChild = originalEditedChildren[i];
                ChangeSetEntry operation = new ChangeSetEntry(id++, currChild, origChild, DomainOperation.Update);
                changeSetEntries.Add(operation);
                currParent.Children.Add(currChild);
                currentAssociatedChildren.Add(operation.Id);
            }

            // add a 2 new children children
            List<Child> newChildren = new List<Child> { new Child(), new Child() };
            foreach (Child c in newChildren)
            {
                ChangeSetEntry operation = new ChangeSetEntry(id++, c, null, DomainOperation.Insert);
                changeSetEntries.Add(operation);
                currParent.Children.Add(c);
                currentAssociatedChildren.Add(operation.Id);
            }

            // add 2 removes by adding operations for the deleted children
            // and setting up the original association
            List<Child> removedChildren = new List<Child> { new Child(), new Child() };
            List<int> deletedChildren = new List<int>();
            foreach (Child c in removedChildren)
            {
                int deletedId = id++;
                changeSetEntries.Add(new ChangeSetEntry(deletedId, c, c, DomainOperation.Delete));
                deletedChildren.Add(deletedId);
            }
            parentUpdateOperation.OriginalAssociations = new Dictionary<string, int[]>() { { "Children", deletedChildren.ToArray() } };
            parentUpdateOperation.Associations = new Dictionary<string, int[]>() { { "Children", currentAssociatedChildren.ToArray() } };
            #endregion

            // verify unmodified children
            ChangeSet cs = new ChangeSet(changeSetEntries);
            IEnumerable<Child> childChanges = cs.GetAssociatedChanges(currParent, p => p.Children, ChangeOperation.None).Cast<Child>();
            Assert.AreEqual(3, childChanges.Count());
            foreach (Child c in childChanges)
            {
                Assert.IsTrue(unmodifiedChildren.Contains(c));
            }

            // verify inserted children
            childChanges = cs.GetAssociatedChanges(currParent, p => p.Children, ChangeOperation.Insert).Cast<Child>();
            Assert.AreEqual(2, childChanges.Count());
            foreach (Child c in childChanges)
            {
                Assert.IsTrue(newChildren.Contains(c));
            }

            // verify deleted children
            childChanges = cs.GetAssociatedChanges(currParent, p => p.Children, ChangeOperation.Delete).Cast<Child>();
            Assert.AreEqual(2, childChanges.Count());
            foreach (Child c in childChanges)
            {
                Assert.IsTrue(removedChildren.Contains(c));
            }

            // verify modified children
            childChanges = cs.GetAssociatedChanges(currParent, p => p.Children, ChangeOperation.Update).Cast<Child>();
            Assert.AreEqual(2, childChanges.Count());
            foreach (Child c in childChanges)
            {
                Assert.IsTrue(currentEditedChildren.Contains(c));
            }

            // verify overload that returns all - should return all unmodified
            // in addition to all changed
            IEnumerable<Child> allChildren = cs.GetAssociatedChanges(currParent, p => p.Children).Cast<Child>();
            Assert.AreEqual(9, allChildren.Count());
            foreach (Child c in allChildren)
            {
                ChangeOperation operationType = cs.GetChangeOperation(c);
                switch (operationType)
                {
                    case ChangeOperation.None:
                        Assert.IsTrue(unmodifiedChildren.Contains(c));
                        break;
                    case ChangeOperation.Insert:
                        Assert.IsTrue(newChildren.Contains(c));
                        break;
                    case ChangeOperation.Update:
                        Assert.IsTrue(currentEditedChildren.Contains(c));
                        break;
                    case ChangeOperation.Delete:
                        Assert.IsTrue(removedChildren.Contains(c));
                        break;
                };
            }

            // verify calls that return empty
            cs = new ChangeSet(new ChangeSetEntry[] { new ChangeSetEntry(1, currParent, null, DomainOperation.None) });
            IEnumerable<Child> children = cs.GetAssociatedChanges(currParent, p => p.Children).Cast<Child>();
            Assert.AreEqual(0, children.Count());
            children = cs.GetAssociatedChanges(currParent, p => p.Children).Cast<Child>();
            Assert.AreEqual(0, children.Count());

            // test null collection properties
            currParent = new Parent { Children = null };
            cs = new ChangeSet(new ChangeSetEntry[] { new ChangeSetEntry(1, currParent, new Parent { Children = null }, DomainOperation.None) });
            Assert.AreEqual(0, cs.GetAssociatedChanges(currParent, p => p.Children).Cast<Child>().Count());
            Assert.AreEqual(0, cs.GetAssociatedChanges(currParent, p => p.Children, ChangeOperation.None).Cast<Child>().Count());
        }