[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()); }