/// <inheritdoc/> protected override object TransformForSerialization(ITypeDescriptor descriptor, object collection) { var dictionaryDescriptor = (DictionaryDescriptor)descriptor; var instance = CreatEmptyContainer(descriptor); CollectionItemIdentifiers identifier; if (!CollectionItemIdHelper.TryGetCollectionItemIds(collection, out identifier)) { identifier = new CollectionItemIdentifiers(); } var keyWithIdType = typeof(KeyWithId <>).MakeGenericType(dictionaryDescriptor.KeyType); foreach (var item in dictionaryDescriptor.GetEnumerator(collection)) { ItemId id; if (!identifier.TryGet(item.Key, out id)) { id = ItemId.New(); identifier.Add(item.Key, id); } var keyWithId = Activator.CreateInstance(keyWithIdType, id, item.Key); instance.Add(keyWithId, item.Value); } return(instance); }
private void AssetContentChanging(object sender, ContentChangeEventArgs e) { var overrideValue = OverrideType.Base; var node = (AssetNode)e.Content.OwnerNode; if (e.ChangeType == ContentChangeType.ValueChange || e.ChangeType == ContentChangeType.CollectionRemove) { // For value change and remove, we store the current override state. if (e.Index == Index.Empty) { overrideValue = node.GetContentOverride(); } else if (!node.IsNonIdentifiableCollectionContent) { overrideValue = node.GetItemOverride(e.Index); } } if (e.ChangeType == ContentChangeType.CollectionRemove) { // For remove, we also collect the id of the item that will be removed, so we can pass it to the Changed event. var itemId = ItemId.Empty; CollectionItemIdentifiers ids; if (CollectionItemIdHelper.TryGetCollectionItemIds(e.Content.Retrieve(), out ids)) { ids.TryGet(e.Index.Value, out itemId); } removedItemIds[e.Content.OwnerNode] = itemId; } if (e.ChangeType == ContentChangeType.CollectionAdd && !node.IsNonIdentifiableCollectionContent) { // If the change is an add, we set the previous override as New so the Undo will try to remove the item instead of resetting to the base value previousOverrides[e.Content.OwnerNode] = OverrideType.New; } previousOverrides[e.Content.OwnerNode] = overrideValue; }
private static void ClearNode(IUndoRedoService actionService, AssetViewModel asset, ItemToReload itemToReload) { // Get the node we want to change var index = itemToReload.GraphPathIndex; var node = itemToReload.GraphPath.GetNode(); var oldValue = node.Retrieve(index); // Apply the change // TODO: Share this code with ContentValueChangeOperation? // TODO: How to better detect CollectionAdd vs ValueChange? ContentChangeType operationType; if (index != NodeIndex.Empty) { if (CollectionItemIdHelper.TryGetCollectionItemIds(node.Retrieve(), out CollectionItemIdentifiers ids)) { itemToReload.ItemId = ids[index.Value]; } operationType = ContentChangeType.CollectionRemove; ((IObjectNode)node).Remove(oldValue, index); } else { operationType = ContentChangeType.ValueChange; ((IMemberNode)node).Update(null); } // Save the change on the stack var operation = new ContentValueChangeOperation(node, operationType, index, oldValue, null, Enumerable.Empty <IDirtiable>()); actionService.PushOperation(operation); string operationName = $"Unload object {oldValue.GetType().Name} in asset {asset.Url}"; actionService.SetName(operation, operationName); }
public void TestCloneCollectionIds() { var obj = new TestObjectWithCollection { Name = "Test", Items = { "aaa", "bbb" } }; var ids = CollectionItemIdHelper.GetCollectionItemIds(obj.Items); ids.Add(0, new ItemId()); ids.Add(1, new ItemId()); ids.MarkAsDeleted(new ItemId()); var clone = AssetCloner.Clone(obj); CollectionItemIdentifiers cloneIds; var idsExist = CollectionItemIdHelper.TryGetCollectionItemIds(clone.Items, out cloneIds); Assert.True(idsExist); Assert.AreEqual(ids.KeyCount, cloneIds.KeyCount); Assert.AreEqual(ids.DeletedCount, cloneIds.DeletedCount); Assert.AreEqual(ids[0], cloneIds[0]); Assert.AreEqual(ids[1], cloneIds[1]); Assert.AreEqual(ids.DeletedItems.Single(), cloneIds.DeletedItems.Single()); clone = AssetCloner.Clone(obj, AssetClonerFlags.RemoveItemIds); idsExist = CollectionItemIdHelper.TryGetCollectionItemIds(clone.Items, out cloneIds); Assert.False(idsExist); }
private void AssetContentChanging(object sender, MemberNodeChangeEventArgs e) { var overrideValue = OverrideType.Base; var node = (AssetMemberNode)e.Member; // For value change and remove, we store the current override state. if (e.ChangeType == ContentChangeType.ValueChange) { overrideValue = node.GetContentOverride(); } else if (!node.IsNonIdentifiableCollectionContent) { overrideValue = node.GetItemOverride(e.Index); if (e.ChangeType == ContentChangeType.CollectionRemove) { // For remove, we also collect the id of the item that will be removed, so we can pass it to the Changed event. var itemId = ItemId.Empty; CollectionItemIdentifiers ids; if (CollectionItemIdHelper.TryGetCollectionItemIds(e.Member.Retrieve(), out ids)) { ids.TryGet(e.Index.Value, out itemId); } removedItemIds[e.Member] = itemId; } } previousOverrides[e.Member] = overrideValue; }
private void OnObjectDeserialized(int i, object newObject) { if (objectReferences != null && newObject != null) { var previousObject = objectReferences[i]; //// If the object is an attached reference, there is no need to copy the shadow object //if (AttachedReferenceManager.GetAttachedReference(previousObject) != null) //{ // return; //} ShadowObject.Copy(previousObject, newObject); // NOTE: we don't use Add because of strings that might be duplicated clonedObjectMapping[previousObject] = newObject; if ((flags & AssetClonerFlags.RemoveItemIds) != AssetClonerFlags.RemoveItemIds) { CollectionItemIdentifiers sourceIds; if (CollectionItemIdHelper.TryGetCollectionItemIds(previousObject, out sourceIds)) { var newIds = CollectionItemIdHelper.GetCollectionItemIds(newObject); sourceIds.CloneInto(newIds, clonedObjectMapping); } } } }
public void TestCollectionItemIdentifierWithDuplicates() { var container = new AssetPropertyGraphContainer(new AssetNodeContainer { NodeBuilder = { NodeFactory = new AssetNodeFactory() } }); var asset = new Types.MyAsset2 { MyStrings = { "aaa", "bbb", "ccc" } }; var ids = CollectionItemIdHelper.GetCollectionItemIds(asset.MyStrings); ids.Add(0, IdentifierGenerator.Get(100)); ids.Add(1, IdentifierGenerator.Get(200)); ids.Add(2, IdentifierGenerator.Get(100)); var assetItem = new AssetItem("MyAsset", asset); Assert.Equal(IdentifierGenerator.Get(100), ids[0]); Assert.Equal(IdentifierGenerator.Get(200), ids[1]); Assert.Equal(IdentifierGenerator.Get(100), ids[2]); var graph = AssetQuantumRegistry.ConstructPropertyGraph(container, assetItem, null); Assert.IsAssignableFrom <AssetObjectNode>(graph.RootNode); Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(asset.MyStrings, out ids)); Assert.Equal(3, ids.KeyCount); Assert.Equal(0, ids.DeletedCount); Assert.Equal(IdentifierGenerator.Get(100), ids[0]); Assert.Equal(IdentifierGenerator.Get(200), ids[1]); Assert.NotEqual(IdentifierGenerator.Get(100), ids[2]); Assert.NotEqual(IdentifierGenerator.Get(200), ids[2]); }
/// <inheritdoc/> protected override object TransformForSerialization(ITypeDescriptor descriptor, object collection) { var instance = CreatEmptyContainer(descriptor); CollectionItemIdentifiers identifier; if (!CollectionItemIdHelper.TryGetCollectionItemIds(collection, out identifier)) { identifier = new CollectionItemIdentifiers(); } var i = 0; foreach (var item in (IEnumerable)collection) { ItemId id; if (!identifier.TryGet(i, out id)) { id = ItemId.New(); identifier.Add(i, id); } instance.Add(id, item); ++i; } return(instance); }
public static YamlAssetPath FromMemberPath([NotNull] MemberPath path, object root) { if (path == null) { throw new ArgumentNullException(nameof(path)); } var result = new YamlAssetPath(); var clone = new MemberPath(); foreach (var item in path.Decompose()) { if (item.MemberDescriptor != null) { clone.Push(item.MemberDescriptor); var member = item.MemberDescriptor.Name; result.PushMember(member); } else { object index = null; if (item is MemberPath.ArrayPathItem arrayItem) { clone.Push(arrayItem.Descriptor, arrayItem.Index); index = arrayItem.Index; } else if (item is MemberPath.CollectionPathItem collectionItem) { clone.Push(collectionItem.Descriptor, collectionItem.Index); index = collectionItem.Index; } else if (item is MemberPath.DictionaryPathItem dictionaryItem) { clone.Push(dictionaryItem.Descriptor, dictionaryItem.Key); index = dictionaryItem.Key; } else if (item is MemberPath.SetPathItem setItem) { clone.Push(setItem.Descriptor, setItem.Index); index = setItem.Index; } if (!CollectionItemIdHelper.TryGetCollectionItemIds(clone.GetValue(root), out CollectionItemIdentifiers ids)) { result.PushIndex(index); } else { var id = ids[index]; // Create a new id if we don't have any so far if (id == ItemId.Empty) { id = ItemId.New(); } result.PushItemId(id); } } } return(result); }
public void TestDictionaryNonIdentifiableItemsDeserialization() { ShadowObject.Enable = true; var stream = new MemoryStream(); var writer = new StreamWriter(stream); writer.Write(YamlDictionaryNonIdentifiable); writer.Flush(); stream.Position = 0; var instance = AssetYamlSerializer.Default.Deserialize(stream); Assert.NotNull(instance); Assert.Equal(typeof(ContainerNonIdentifiableDictionary), instance.GetType()); var obj = (ContainerNonIdentifiableDictionary)instance; Assert.Equal("Root", obj.Name); Assert.Equal(2, obj.Objects.Count); Assert.Equal("aaa", obj.Objects["AAA"].Name); Assert.Equal(2, obj.Objects["AAA"].Strings.Count); Assert.Equal("bbb", obj.Objects["AAA"].Strings[0]); Assert.Equal("ccc", obj.Objects["AAA"].Strings[1]); Assert.Equal("ddd", obj.Objects["BBB"].Name); Assert.Equal(2, obj.Objects["BBB"].Strings.Count); Assert.Equal("eee", obj.Objects["BBB"].Strings[0]); Assert.Equal("fff", obj.Objects["BBB"].Strings[1]); var objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects); Assert.Equal(IdentifierGenerator.Get(2), objectIds["AAA"]); Assert.Equal(IdentifierGenerator.Get(1), objectIds["BBB"]); objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects["AAA"].Strings); Assert.Equal(IdentifierGenerator.Get(5), objectIds[0]); Assert.Equal(IdentifierGenerator.Get(6), objectIds[1]); objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.Objects["BBB"].Strings); Assert.Equal(IdentifierGenerator.Get(7), objectIds[0]); Assert.Equal(IdentifierGenerator.Get(8), objectIds[1]); Assert.Equal(2, obj.NonIdentifiableObjects.Count); Assert.Equal("ggg", obj.NonIdentifiableObjects["CCC"].Name); Assert.Equal(2, obj.NonIdentifiableObjects["CCC"].Strings.Count); Assert.Equal("hhh", obj.NonIdentifiableObjects["CCC"].Strings[0]); Assert.Equal("iii", obj.NonIdentifiableObjects["CCC"].Strings[1]); Assert.Equal("jjj", obj.NonIdentifiableObjects["DDD"].Name); Assert.Equal(2, obj.NonIdentifiableObjects["DDD"].Strings.Count); Assert.Equal("kkk", obj.NonIdentifiableObjects["DDD"].Strings[0]); Assert.Equal("lll", obj.NonIdentifiableObjects["DDD"].Strings[1]); Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj.NonIdentifiableObjects, out objectIds)); objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects); Assert.Equal(0, objectIds.KeyCount); Assert.Equal(0, objectIds.DeletedCount); objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects["CCC"].Strings); Assert.Equal(IdentifierGenerator.Get(9), objectIds[0]); Assert.Equal(IdentifierGenerator.Get(10), objectIds[1]); objectIds = CollectionItemIdHelper.GetCollectionItemIds(obj.NonIdentifiableObjects["DDD"].Strings); Assert.Equal(IdentifierGenerator.Get(11), objectIds[0]); Assert.Equal(IdentifierGenerator.Get(12), objectIds[1]); }
private bool TryGetCollectionItemIds(object instance, out CollectionItemIdentifiers itemIds) { if (collectionItemIdentifiers != null) { itemIds = collectionItemIdentifiers; return(true); } var result = CollectionItemIdHelper.TryGetCollectionItemIds(instance, out collectionItemIdentifiers); itemIds = collectionItemIdentifiers; return(result); }
public override void VisitDictionary(object dictionary, DictionaryDescriptor descriptor) { CollectionItemIdentifiers itemIds; if (inNonIdentifiableType == 0 && !CollectionItemIdHelper.TryGetCollectionItemIds(dictionary, out itemIds)) { itemIds = CollectionItemIdHelper.GetCollectionItemIds(dictionary); foreach (var element in descriptor.GetEnumerator(dictionary)) { itemIds.Add(element.Key, ItemId.New()); } } base.VisitDictionary(dictionary, descriptor); }
public override void VisitArray(Array array, ArrayDescriptor descriptor) { CollectionItemIdentifiers itemIds; if (inNonIdentifiableType == 0 && !CollectionItemIdHelper.TryGetCollectionItemIds(array, out itemIds)) { itemIds = CollectionItemIdHelper.GetCollectionItemIds(array); for (var i = 0; i < array.Length; ++i) { itemIds.Add(i, ItemId.New()); } } base.VisitArray(array, descriptor); }
public override void VisitCollection(IEnumerable collection, CollectionDescriptor descriptor) { CollectionItemIdentifiers itemIds; if (inNonIdentifiableType == 0 && !CollectionItemIdHelper.TryGetCollectionItemIds(collection, out itemIds)) { itemIds = CollectionItemIdHelper.GetCollectionItemIds(collection); var count = descriptor.GetCollectionCount(collection); for (var i = 0; i < count; ++i) { itemIds.Add(i, ItemId.New()); } } base.VisitCollection(collection, descriptor); }
/// <summary> /// Fixes up the <see cref="CollectionItemIdentifiers"/> of a collection by generating new ids if there are any duplicate. /// </summary> /// <param name="collection">The collection to fix up.</param> /// <remarks>This method doesn't handle collections in derived objects that will be desynchronized afterwards.</remarks> private void Fixup(object collection) { CollectionItemIdentifiers itemIds; if (CollectionItemIdHelper.TryGetCollectionItemIds(collection, out itemIds)) { var items = new HashSet <ItemId>(); var localCopy = new CollectionItemIdentifiers(); itemIds.CloneInto(localCopy, null); foreach (var id in localCopy) { if (!items.Add(id.Value)) { logger?.Warning($"Duplicate item identifier [{id.Value}] in collection {CurrentPath} of asset [{assetItem.Location}]. Generating a new identifier to remove the duplicate entry."); itemIds[id.Key] = ItemId.New(); } } } }
private void AssetContentChanged(object sender, MemberNodeChangeEventArgs e) { var previousOverride = previousOverrides[e.Member]; previousOverrides.Remove(e.Member); var itemId = ItemId.Empty; var overrideValue = OverrideType.Base; var node = (AssetMemberNode)e.Member; if (e.ChangeType == ContentChangeType.ValueChange) { // No index, we're changing an object that is not in a collection, let's just retrieve it's override status. overrideValue = node.GetContentOverride(); } else if (e.ChangeType == ContentChangeType.CollectionUpdate || e.ChangeType == ContentChangeType.CollectionAdd) { // We're changing an item of a collection. If the collection has identifiable items, retrieve the override status of the item. if (!node.IsNonIdentifiableCollectionContent) { overrideValue = node.GetItemOverride(e.Index); // Also retrieve the id of the modified item (this should fail only if the collection doesn't have identifiable items) CollectionItemIdentifiers ids; if (CollectionItemIdHelper.TryGetCollectionItemIds(e.Member.Retrieve(), out ids)) { ids.TryGet(e.Index.Value, out itemId); } } } else { // When deleting we are always overriding (unless there is no base or non-identifiable items) if (!node.IsNonIdentifiableCollectionContent) { overrideValue = node.BaseContent != null && !UpdatingPropertyFromBase ? OverrideType.New : OverrideType.Base; itemId = removedItemIds[e.Member]; removedItemIds.Remove(e.Member); } } Changed?.Invoke(sender, new AssetMemberNodeChangeEventArgs(e, previousOverride, overrideValue, itemId)); }
public void TestCollectionConstruction() { var container = new AssetPropertyGraphContainer(new PackageSession(), new AssetNodeContainer()); var asset = new Types.MyAsset2 { MyStrings = { "aaa", "bbb", "ccc" } }; var assetItem = new AssetItem("MyAsset", asset); var graph = AssetQuantumRegistry.ConstructPropertyGraph(container, assetItem, null); Assert.IsAssignableFrom <AssetNode>(graph.RootNode); CollectionItemIdentifiers ids; Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(asset.MyStrings, out ids)); Assert.AreEqual(3, ids.KeyCount); Assert.AreEqual(0, ids.DeletedCount); Assert.True(ids.ContainsKey(0)); Assert.True(ids.ContainsKey(1)); Assert.True(ids.ContainsKey(2)); }
/// <inheritdoc/> protected override void CreateOrTransformObject(ref ObjectContext objectContext) { base.CreateOrTransformObject(ref objectContext); // Allow to deserialize the old way if (!objectContext.SerializerContext.IsSerializing && objectContext.Reader.Accept <SequenceStart>()) { return; } // Ignore collections flagged as having non-identifiable items if (!AreCollectionItemsIdentifiable(ref objectContext)) { return; } // Store the information on the actual instance before transforming. var info = new InstanceInfo(objectContext.Instance, objectContext.Descriptor); objectContext.Properties.Add(InstanceInfoKey, info); if (objectContext.SerializerContext.IsSerializing && objectContext.Instance != null) { // Store deleted items in the context CollectionItemIdentifiers identifier; if (CollectionItemIdHelper.TryGetCollectionItemIds(objectContext.Instance, out identifier)) { var deletedItems = identifier.DeletedItems.ToList(); deletedItems.Sort(); objectContext.Properties.Add(DeletedItemsKey, deletedItems); } // We're serializing, transform the collection to a dictionary of <id, items> objectContext.Instance = TransformForSerialization(objectContext.Descriptor, objectContext.Instance); } else { // We're deserializing, create an empty dictionary of <id, items> objectContext.Instance = CreatEmptyContainer(objectContext.Descriptor); } }
private void OnObjectDeserialized(int i, object newObject) { if (objectReferences != null && newObject != null) { var previousObject = objectReferences[i]; //// If the object is an attached reference, there is no need to copy the shadow object //if (AttachedReferenceManager.GetAttachedReference(previousObject) != null) //{ // return; //} ShadowObject.Copy(previousObject, newObject); // NOTE: we don't use Add because of strings that might be duplicated clonedObjectMapping[previousObject] = newObject; if ((flags & AssetClonerFlags.RemoveItemIds) != AssetClonerFlags.RemoveItemIds) { CollectionItemIdentifiers sourceIds; if (CollectionItemIdHelper.TryGetCollectionItemIds(previousObject, out sourceIds)) { var newIds = CollectionItemIdHelper.GetCollectionItemIds(newObject); sourceIds.CloneInto(newIds, clonedObjectMapping); } } if ((flags & AssetClonerFlags.GenerateNewIdsForIdentifiableObjects) == AssetClonerFlags.GenerateNewIdsForIdentifiableObjects) { var identifiable = newObject as IIdentifiable; if (identifiable != null) { cloningIdRemapping = cloningIdRemapping ?? new Dictionary <Guid, Guid>(); var newId = Guid.NewGuid(); cloningIdRemapping[identifiable.Id] = newId; identifiable.Id = newId; } } } }
protected List <DebugAssetNodeViewModel> UpdateChildren() { var list = new List <DebugAssetNodeViewModel>(); if (Node != null && Registered) { var objNode = Node as IObjectNode; if (objNode != null) { foreach (var child in objNode.Members) { list.Add(new DebugAssetChildNodeViewModel(ServiceProvider, child, NodeIndex.Empty, null, LinkChild, registeredNodes)); } } if (Node.IsReference) { var objReference = (Node as IMemberNode)?.TargetReference; if (objReference != null) { list.Add(new DebugAssetChildNodeViewModel(ServiceProvider, objReference.TargetNode, objReference.Index, null, LinkRef, registeredNodes)); } else { CollectionItemIdHelper.TryGetCollectionItemIds(Node.Retrieve(), out var itemIds); foreach (var reference in ((IObjectNode)Node).ItemReferences) { ItemId?itemId = null; if (itemIds != null && itemIds.TryGet(reference.Index.Value, out var retrievedItemId)) { itemId = retrievedItemId; } list.Add(new DebugAssetChildNodeViewModel(ServiceProvider, reference.TargetNode, reference.Index, itemId, LinkRef, registeredNodes)); } } } } return(list); }
public void TestIdsGeneration() { ShadowObject.Enable = true; CollectionItemIdentifiers ids; var obj1 = new ContainerCollection("Root") { Strings = { "aaa", "bbb", "ccc" }, Objects = { new ContainerCollection("obj1"), new ContainerCollection("obj2") } }; var hashSet = new HashSet <ItemId>(); Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Strings, out ids)); Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Strings, out ids)); Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Objects, out ids)); Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Objects, out ids)); AssetCollectionItemIdHelper.GenerateMissingItemIds(obj1); Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Strings, out ids)); Assert.Equal(3, ids.KeyCount); Assert.Equal(0, ids.DeletedCount); Assert.True(ids.ContainsKey(0)); Assert.True(ids.ContainsKey(1)); Assert.True(ids.ContainsKey(2)); hashSet.Add(ids[0]); hashSet.Add(ids[1]); hashSet.Add(ids[2]); Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(obj1.Objects, out ids)); Assert.Equal(2, ids.KeyCount); Assert.Equal(0, ids.DeletedCount); Assert.True(ids.ContainsKey(0)); Assert.True(ids.ContainsKey(1)); hashSet.Add(ids[0]); hashSet.Add(ids[1]); Assert.Equal(5, hashSet.Count); var obj2 = new ContainerDictionary("Root") { Strings = { { GuidGenerator.Get(200), "aaa" }, { GuidGenerator.Get(100), "bbb" }, { GuidGenerator.Get(300), "ccc" } }, Objects = { { "key3", new ContainerCollection("obj1") }, { "key4", new ContainerCollection("obj2") } }, }; hashSet.Clear(); Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Strings, out ids)); Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Strings, out ids)); Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Objects, out ids)); Assert.False(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Objects, out ids)); AssetCollectionItemIdHelper.GenerateMissingItemIds(obj2); Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Strings, out ids)); Assert.Equal(3, ids.KeyCount); Assert.Equal(0, ids.DeletedCount); Assert.True(ids.ContainsKey(GuidGenerator.Get(200))); Assert.True(ids.ContainsKey(GuidGenerator.Get(100))); Assert.True(ids.ContainsKey(GuidGenerator.Get(300))); hashSet.Add(ids[GuidGenerator.Get(200)]); hashSet.Add(ids[GuidGenerator.Get(100)]); hashSet.Add(ids[GuidGenerator.Get(300)]); Assert.True(CollectionItemIdHelper.TryGetCollectionItemIds(obj2.Objects, out ids)); Assert.Equal(2, ids.KeyCount); Assert.Equal(0, ids.DeletedCount); Assert.True(ids.ContainsKey("key3")); Assert.True(ids.ContainsKey("key4")); hashSet.Add(ids["key3"]); hashSet.Add(ids["key4"]); Assert.Equal(5, hashSet.Count); }