/// <summary> /// Updates the parent nodes list. /// If the node state is None, Created or Modified new node is added to the parent nodes list of all the child nodes (child nodes are nodes which are referenced by the new node as properties). /// If the node state is Removed new node is removed from the parent nodes list of all the child nodes. /// </summary> /// <param name="newId"></param> /// <param name="nodeState"></param> /// <param name="delta"></param> /// <param name="edge"></param> /// <param name="childNode"></param> /// <param name="parentNode"></param> private static void UpdateParentNode(Guid newId, NodeState nodeState, DirectNodeProviderUnsafe <Guid, object, EdgeData> delta, Edge <Guid, EdgeData> edge, Node <Guid, object, EdgeData> childNode, Node <Guid, object, EdgeData> parentNode) { switch (nodeState) { case NodeState.None: case NodeState.Created: AddParentToChildNode(newId, childNode); break; case NodeState.Modified: if (!parentNode.Previous.Equals(Guid.Empty) && childNode.ParentNodes.Contains(parentNode.Previous)) { childNode.ParentNodes.Remove(parentNode.Previous); } AddParentToChildNode(newId, childNode); break; case NodeState.Removed: childNode.ParentNodes.Remove(newId); break; default: break; } }
/// <summary> /// Creates new instance of Workspace type /// </summary> internal Workspace(Guid snapshotId, TimeSpan timeout, INodeProvider <Guid, object, EdgeData> nodeProvider, IWorkspaceFacade commitTarget, ProxyCreatorService proxyCreatorService, TypesService typesService, IsolationLevel isolationLevel, IProxyMap immutableProxyMap) { this.workspaceId = Guid.NewGuid(); this.thread = Thread.CurrentThread; if (!typeof(TDataType).IsInterface) { throw new ArgumentException("Interface type expected: " + typeof(TDataType).AssemblyQualifiedName); } this.snapshotId = snapshotId; this.nodeProvider = nodeProvider; this.proxyCreatorService = proxyCreatorService; this.typesService = typesService; this.isolationLevel = isolationLevel; this.workspaceFacade = commitTarget; this.immutableProxyMap = immutableProxyMap; workspaceFacade.OpenWorkspace(workspaceId, snapshotId, isolationLevel, timeout); if (isolationLevel == IsolationLevel.ReadOnly) { // Rely directly on parent provider if read only this.objectInstancesService = new ObjectInstancesService(nodeProvider, typesService); this.immutableInstancesService = new ObjectInstancesService(nodeProvider, typesService); this.collectionInstancesService = new CollectionInstancesService(nodeProvider, typesService); this.dictionaryInstancesService = new DictionaryInstancesService(nodeProvider, typesService); } else { // Construct isolated provider for local changes var isolatedStorage = new DirectNodeProviderUnsafe <Guid, object, EdgeData>(new MemoryStorageUnsafe <Guid, object>(), false); isolatedProvider = new IsolatedNodeProvider(nodeProvider, isolatedStorage, thread); this.objectInstancesService = new ObjectInstancesService(isolatedProvider, typesService); this.immutableInstancesService = new ObjectInstancesService(nodeProvider, typesService); this.collectionInstancesService = new CollectionInstancesService(isolatedProvider, typesService); this.dictionaryInstancesService = new DictionaryInstancesService(isolatedProvider, typesService); } this.runtimeProxyFacade = new RuntimeProxyFacade(typesService, objectInstancesService, immutableInstancesService, collectionInstancesService, new CollectionInstancesService(nodeProvider, typesService), dictionaryInstancesService, new DictionaryInstancesService(nodeProvider, typesService), mutableProxyMap, immutableProxyMap, proxyCreatorService); // Initialize root data proxy var rootObjectId = commitTarget.GetRootObjectId(snapshotId); rootProxy = proxyCreatorService.NewObject <TDataType>(runtimeProxyFacade, rootObjectId, isolationLevel == IsolationLevel.ReadOnly); if (isolationLevel == IsolationLevel.ReadOnly) { immutableProxyMap.AddProxy(rootObjectId, rootProxy); } else { mutableProxyMap.AddProxy(rootObjectId, rootProxy); } }
/// <summary> /// Open an workspace over an archive. Archive must have the same schema as the main context. /// </summary> /// <typeparam name="TDataType">Data type of root archive object.</typeparam> /// <param name="storage">Storage which contains the archive.</param> /// <returns>A read-only workspace which allows data reading from an archive.</returns> public Workspace <TDataType> OpenArchive <TDataType>(IKeyValueStorage <Guid, object> storage) { INodeProvider <Guid, object, EdgeData> archiveProvider = new DirectNodeProviderUnsafe <Guid, object, EdgeData>(storage, false); if (storage is ISerializingStorage) { // Inject the serializer ((ISerializingStorage)storage).Serializer = objectSerializationService; } return(new Workspace <TDataType>(Guid.Empty, TimeSpan.Zero, archiveProvider, new ArchiveWorkspaceFacade(archiveProvider), proxyCreatorService, typesService, IsolationLevel.ReadOnly, new LimitedProxyMap(Properties.Settings.Default.ObjectCacheMinimumCount, Properties.Settings.Default.ObjectCacheMaximumCount))); }
private AppendableChangeSet <Guid, object, EdgeData> CreateMergedChangeSet(Guid latestSnapshot, Hashtable subTree, IsolatedChangeSet <Guid, object, EdgeData> changeSet, Dictionary <Guid, Guid> intermediateChanges) { #if DEBUG Debug.WriteLine("Merging over intermediate changes"); #endif // We create node provider which will host changes to the last snapshot made by the merge process var isolatedNodes = new DirectNodeProviderUnsafe <Guid, object, EdgeData>(new MemoryStorageUnsafe <Guid, object>(), false); // Perform merging on the current thread IsolatedNodeProvider destinationProvider = new IsolatedNodeProvider(nodes, isolatedNodes, Thread.CurrentThread); IsolatedNodeProvider sourceProvider = new IsolatedNodeProvider(nodes, changeSet.Nodes, Thread.CurrentThread); // Make changes from incoming changes within a subtree MergeRecursive(latestSnapshot, new RecursiveResolutionParameters(subTree, destinationProvider, sourceProvider, changeSet, intermediateChanges, new Hashtable())); // We create an appendable change set from changes made to last snapshot, defining a new snapshot return(CreateAppendableChangeSet(latestSnapshot, Guid.NewGuid(), destinationProvider.GetChanges(latestSnapshot))); }
/// <summary> /// For the given nod, goes through all the child nodes and adds that node to the parent node list of the child nodes. /// </summary> /// <param name="newId"></param> /// <param name="parentNode"></param> /// <param name="nodeState"></param> /// <param name="delta">Node provider which contains nodes which will be saved at the end of the commit process</param> private void UpdateParentNodes(Guid newId, Node<Guid, object, EdgeData> parentNode, NodeState nodeState, DirectNodeProviderUnsafe<Guid, object, EdgeData> delta, IsolatedChangeSet<Guid, object, EdgeData> changeSet) { foreach (Edge<Guid, EdgeData> edge in parentNode.Edges.Values) { if ((edge.Data as EdgeData).Semantic == EdgeType.Property && (edge.Data.Flags & EdgeFlags.StoreParentNodes) == EdgeFlags.StoreParentNodes) { var childNode = GetNode(edge.ToNodeId, changeSet); if (childNode != null) { switch (childNode.NodeType) { case NodeType.Object: UpdateParentNode(newId, nodeState, delta, edge, childNode, parentNode); break; case NodeType.Collection: case NodeType.Dictionary: foreach (Edge<Guid, EdgeData> collectionEdge in childNode.Edges.Values) { if (collectionEdge.Data.Semantic.Equals(EdgeType.ListItem)) { UpdateParentNode(newId, nodeState, delta, collectionEdge, GetNode(collectionEdge.ToNodeId, changeSet), parentNode); } } break; default: throw new NotImplementedException("NodeType=" + childNode.NodeType); } } } } }
private AppendableChangeSet<Guid, object, EdgeData> CreateMergedChangeSet(Guid latestSnapshot, Hashtable subTree, IsolatedChangeSet<Guid, object, EdgeData> changeSet, Dictionary<Guid, Guid> intermediateChanges) { #if DEBUG Debug.WriteLine("Merging over intermediate changes"); #endif // We create node provider which will host changes to the last snapshot made by the merge process var isolatedNodes = new DirectNodeProviderUnsafe<Guid,object, EdgeData>(new MemoryStorageUnsafe<Guid, object>(), false); // Perform merging on the current thread IsolatedNodeProvider destinationProvider = new IsolatedNodeProvider(nodes, isolatedNodes, Thread.CurrentThread); IsolatedNodeProvider sourceProvider = new IsolatedNodeProvider(nodes, changeSet.Nodes, Thread.CurrentThread); // Make changes from incoming changes within a subtree MergeRecursive(latestSnapshot, new RecursiveResolutionParameters(subTree, destinationProvider, sourceProvider, changeSet, intermediateChanges, new Hashtable())); // We create an appendable change set from changes made to last snapshot, defining a new snapshot return CreateAppendableChangeSet(latestSnapshot, Guid.NewGuid(), destinationProvider.GetChanges(latestSnapshot)); }
/// <summary> /// Creates new delta tree describing the new snapshot /// </summary> /// <param name="baseSnapshotId">Base snapshot of the change set</param> /// <param name="newSnapshotId">New snapshot of the change set</param> /// <returns>Delta tree nodes</returns> private INodeProvider<Guid, object, EdgeData> CreateAppendableChangeSetTree(Guid baseSnapshotId, Guid newSnapshotId, IKeyValueStorage<Guid, object> storage, Dictionary<Guid, Guid> nodeMapping, Dictionary<Guid, NodeState> nodeStates, IsolatedChangeSet<Guid, object, EdgeData> changeSet, Hashtable reusedNodes) { DirectNodeProviderUnsafe<Guid, object, EdgeData> delta = new DirectNodeProviderUnsafe<Guid, object, EdgeData>(storage, storage is IForceUpdateStorage); // Create snapshot in delta nodeMapping.Add(baseSnapshotId, newSnapshotId); var snapshotNode = new Node<Guid, object, EdgeData>(NodeType.Snapshot, null); snapshotNode.Previous = baseSnapshotId; // Link to root object snapshotNode.AddEdge(new Edge<Guid, EdgeData>(snapshotsService.GetRootObjectId(baseSnapshotId), new EdgeData(EdgeType.RootObject, null))); // Set node to provider delta.SetNode(newSnapshotId, snapshotNode); // Add all changes and parent nodes foreach (Guid nodeId in changeSet.Nodes.EnumerateNodes()) { AddNodeToAppendableTreeRecursive(delta, nodeId, nodeMapping, nodeStates, changeSet); } // Prepare list of unreferenced node IDs Dictionary<Guid, Collection<Guid>> references = new Dictionary<Guid, Collection<Guid>>(); // Resolve edges based on mapping foreach (Guid nodeId in delta.EnumerateNodes()) { references.Add(nodeId, new Collection<Guid>()); } // Resolve edges based on mapping foreach (Guid nodeId in delta.EnumerateNodes()) { var node = delta.GetNode(nodeId, NodeAccess.ReadWrite); foreach (var edge in node.Edges.Values) { // Permanent edges should not be touched if (!Utils.IsPermanentEdge(edge)) { // Reroute based on node mapping if (nodeMapping.ContainsKey(edge.ToNodeId)) { edge.ToNodeId = nodeMapping[edge.ToNodeId]; } else { if (!reusedNodes.ContainsKey(edge.ToNodeId)) { reusedNodes.Add(edge.ToNodeId, null); } } // Change edge data if it points to changed node if (edge.Data.Data != null && edge.Data.Data is Guid) { if (nodeMapping.ContainsKey((Guid)edge.Data.Data)) { edge.Data = new EdgeData(edge.Data.Semantic, nodeMapping[(Guid)edge.Data.Data]); } } } else { if (!reusedNodes.ContainsKey(edge.ToNodeId)) { reusedNodes.Add(edge.ToNodeId, null); } } if (references.ContainsKey(edge.ToNodeId)) { references[edge.ToNodeId].Add(nodeId); } } } // Remove remaining unreferenced nodes bool removed = false; do { removed = false; foreach (Guid key in delta.EnumerateNodes()) { // There are no references to the key if ((key!=newSnapshotId) && (references[key].Count == 0)) { #if DEBUG Debug.WriteLine("Removed unreferenced key " + key); #endif delta.Remove(key); reusedNodes.Remove(key); nodeStates.Remove(key); foreach (Guid otherKey in references.Keys) { references[otherKey].Remove(key); } foreach (var mappingKey in nodeMapping.Keys) { if (nodeMapping[mappingKey] == key) { nodeMapping.Remove(mappingKey); break; } } removed = true; break; } } } while (removed); // Isolated provider nodes have been corrupted, perform clear changeSet.Nodes.Clear(); changeSet.NodeStates.Clear(); return delta; }
/// <summary> /// Adds node to delta with recursive pass through node parents /// </summary> /// <param name="delta">Provider which will accept new nodes</param> /// <param name="nodeId">Node ID to add</param> /// <param name="nodeMapping">Mapping between (old ID)->(new ID)</param> private void AddNodeToAppendableTreeRecursive(DirectNodeProviderUnsafe<Guid, object, EdgeData> delta, Guid nodeId, Dictionary<Guid, Guid> nodeMapping, Dictionary<Guid, NodeState> nodeStates, IsolatedChangeSet<Guid, object, EdgeData> changeSet) { // Skip already added nodes if (!nodeMapping.ContainsKey(nodeId)) { // Generate new ID Guid newId = Guid.NewGuid(); // Check node state NodeState nodeState = NodeState.None; changeSet.NodeStates.TryGetValue(nodeId, out nodeState); switch (nodeState) { case NodeState.None: { // Register in the mapping nodeMapping.Add(nodeId, newId); // Get undelrying node var node = nodes.GetNode(nodeId, NodeAccess.Read); var newNode = CloneNode(node); // Set node to commited newNode.Commited = true; // Create edge to previous node newNode.Previous = nodeId; // New node is created which is copied from underlying provider delta.SetNode(newId, newNode); // This change is defined as modification nodeStates.Add(newId, NodeState.Modified); // Add node parents from the current snapshot using (var enumerator = mutableParentProvider.ParentEdges(changeSet.SourceSnapshotId, nodeId)) { if (enumerator != null) { while (enumerator.MoveNext()) { AddNodeToAppendableTreeRecursive(delta, enumerator.Current.ToNodeId, nodeMapping, nodeStates, changeSet); } } } // Update parent nodes UpdateParentNodes(newId, newNode, nodeState, delta, changeSet); } break; case NodeState.Created: { // Read the node var node = changeSet.Nodes.GetNode(nodeId, NodeAccess.ReadWrite); // Set node to commited node.Commited = true; // There is no previous node.Previous = Guid.Empty; // Store to delta delta.SetNode(nodeId, node); // This change is defined as creation nodeStates.Add(nodeId, NodeState.Created); // Update parent nodes UpdateParentNodes(nodeId, node, nodeState, delta, changeSet); } break; case NodeState.Modified: { // Register in the mapping nodeMapping.Add(nodeId, newId); // Read the node var node = changeSet.Nodes.GetNode(nodeId, NodeAccess.ReadWrite); // Set node to commited node.Commited = true; // Set the previous node.Previous = nodeId; // Store to delta delta.SetNode(newId, node); // This change is defined as modification nodeStates.Add(newId, NodeState.Modified); // Add node parents from the current snapshot using (var enumerator = mutableParentProvider.ParentEdges(changeSet.SourceSnapshotId, nodeId)) { while (enumerator.MoveNext()) { AddNodeToAppendableTreeRecursive(delta, enumerator.Current.ToNodeId, nodeMapping, nodeStates, changeSet); } } // Update parent nodes UpdateParentNodes(newId, node, nodeState, delta, changeSet); } break; case NodeState.Removed: { // Update parent nodes var node = GetNode(nodeId, changeSet); UpdateParentNodes(nodeId, node, nodeState, delta, changeSet); } break; default: throw new ArgumentException(nodeState.ToString()); } } }
/// <summary> /// Updates the parent nodes list. /// If the node state is None, Created or Modified new node is added to the parent nodes list of all the child nodes (child nodes are nodes which are referenced by the new node as properties). /// If the node state is Removed new node is removed from the parent nodes list of all the child nodes. /// </summary> /// <param name="newId"></param> /// <param name="nodeState"></param> /// <param name="delta"></param> /// <param name="edge"></param> /// <param name="childNode"></param> /// <param name="parentNode"></param> private static void UpdateParentNode(Guid newId, NodeState nodeState, DirectNodeProviderUnsafe<Guid, object, EdgeData> delta, Edge<Guid, EdgeData> edge, Node<Guid, object, EdgeData> childNode, Node<Guid, object, EdgeData> parentNode) { switch (nodeState) { case NodeState.None: case NodeState.Created: AddParentToChildNode(newId, childNode); break; case NodeState.Modified: if (!parentNode.Previous.Equals(Guid.Empty) && childNode.ParentNodes.Contains(parentNode.Previous)) { childNode.ParentNodes.Remove(parentNode.Previous); } AddParentToChildNode(newId, childNode); break; case NodeState.Removed: childNode.ParentNodes.Remove(newId); break; default: break; } }
/// <summary> /// For the given nod, goes through all the child nodes and adds that node to the parent node list of the child nodes. /// </summary> /// <param name="newId"></param> /// <param name="parentNode"></param> /// <param name="nodeState"></param> /// <param name="delta">Node provider which contains nodes which will be saved at the end of the commit process</param> private void UpdateParentNodes(Guid newId, Node <Guid, object, EdgeData> parentNode, NodeState nodeState, DirectNodeProviderUnsafe <Guid, object, EdgeData> delta, IsolatedChangeSet <Guid, object, EdgeData> changeSet) { foreach (Edge <Guid, EdgeData> edge in parentNode.Edges.Values) { if ((edge.Data as EdgeData).Semantic == EdgeType.Property && (edge.Data.Flags & EdgeFlags.StoreParentNodes) == EdgeFlags.StoreParentNodes) { var childNode = GetNode(edge.ToNodeId, changeSet); if (childNode != null) { switch (childNode.NodeType) { case NodeType.Object: UpdateParentNode(newId, nodeState, delta, edge, childNode, parentNode); break; case NodeType.Collection: case NodeType.Dictionary: foreach (Edge <Guid, EdgeData> collectionEdge in childNode.Edges.Values) { if (collectionEdge.Data.Semantic.Equals(EdgeType.ListItem)) { UpdateParentNode(newId, nodeState, delta, collectionEdge, GetNode(collectionEdge.ToNodeId, changeSet), parentNode); } } break; default: throw new NotImplementedException("NodeType=" + childNode.NodeType); } } } } }
/// <summary> /// Adds node to delta with recursive pass through node parents /// </summary> /// <param name="delta">Provider which will accept new nodes</param> /// <param name="nodeId">Node ID to add</param> /// <param name="nodeMapping">Mapping between (old ID)->(new ID)</param> private void AddNodeToAppendableTreeRecursive(DirectNodeProviderUnsafe <Guid, object, EdgeData> delta, Guid nodeId, Dictionary <Guid, Guid> nodeMapping, Dictionary <Guid, NodeState> nodeStates, IsolatedChangeSet <Guid, object, EdgeData> changeSet) { // Skip already added nodes if (!nodeMapping.ContainsKey(nodeId)) { // Generate new ID Guid newId = Guid.NewGuid(); // Check node state NodeState nodeState = NodeState.None; changeSet.NodeStates.TryGetValue(nodeId, out nodeState); switch (nodeState) { case NodeState.None: { // Register in the mapping nodeMapping.Add(nodeId, newId); // Get undelrying node var node = nodes.GetNode(nodeId, NodeAccess.Read); var newNode = CloneNode(node); // Set node to commited newNode.Commited = true; // Create edge to previous node newNode.Previous = nodeId; // New node is created which is copied from underlying provider delta.SetNode(newId, newNode); // This change is defined as modification nodeStates.Add(newId, NodeState.Modified); // Add node parents from the current snapshot using (var enumerator = mutableParentProvider.ParentEdges(changeSet.SourceSnapshotId, nodeId)) { if (enumerator != null) { while (enumerator.MoveNext()) { AddNodeToAppendableTreeRecursive(delta, enumerator.Current.ToNodeId, nodeMapping, nodeStates, changeSet); } } } // Update parent nodes UpdateParentNodes(newId, newNode, nodeState, delta, changeSet); } break; case NodeState.Created: { // Read the node var node = changeSet.Nodes.GetNode(nodeId, NodeAccess.ReadWrite); // Set node to commited node.Commited = true; // There is no previous node.Previous = Guid.Empty; // Store to delta delta.SetNode(nodeId, node); // This change is defined as creation nodeStates.Add(nodeId, NodeState.Created); // Update parent nodes UpdateParentNodes(nodeId, node, nodeState, delta, changeSet); } break; case NodeState.Modified: { // Register in the mapping nodeMapping.Add(nodeId, newId); // Read the node var node = changeSet.Nodes.GetNode(nodeId, NodeAccess.ReadWrite); // Set node to commited node.Commited = true; // Set the previous node.Previous = nodeId; // Store to delta delta.SetNode(newId, node); // This change is defined as modification nodeStates.Add(newId, NodeState.Modified); // Add node parents from the current snapshot using (var enumerator = mutableParentProvider.ParentEdges(changeSet.SourceSnapshotId, nodeId)) { while (enumerator.MoveNext()) { AddNodeToAppendableTreeRecursive(delta, enumerator.Current.ToNodeId, nodeMapping, nodeStates, changeSet); } } // Update parent nodes UpdateParentNodes(newId, node, nodeState, delta, changeSet); } break; case NodeState.Removed: { // Update parent nodes var node = GetNode(nodeId, changeSet); UpdateParentNodes(nodeId, node, nodeState, delta, changeSet); } break; default: throw new ArgumentException(nodeState.ToString()); } } }
/// <summary> /// Creates new delta tree describing the new snapshot /// </summary> /// <param name="baseSnapshotId">Base snapshot of the change set</param> /// <param name="newSnapshotId">New snapshot of the change set</param> /// <returns>Delta tree nodes</returns> private INodeProvider <Guid, object, EdgeData> CreateAppendableChangeSetTree(Guid baseSnapshotId, Guid newSnapshotId, IKeyValueStorage <Guid, object> storage, Dictionary <Guid, Guid> nodeMapping, Dictionary <Guid, NodeState> nodeStates, IsolatedChangeSet <Guid, object, EdgeData> changeSet, Hashtable reusedNodes) { DirectNodeProviderUnsafe <Guid, object, EdgeData> delta = new DirectNodeProviderUnsafe <Guid, object, EdgeData>(storage, storage is IForceUpdateStorage); // Create snapshot in delta nodeMapping.Add(baseSnapshotId, newSnapshotId); var snapshotNode = new Node <Guid, object, EdgeData>(NodeType.Snapshot, null); snapshotNode.Previous = baseSnapshotId; // Link to root object snapshotNode.AddEdge(new Edge <Guid, EdgeData>(snapshotsService.GetRootObjectId(baseSnapshotId), new EdgeData(EdgeType.RootObject, null))); // Set node to provider delta.SetNode(newSnapshotId, snapshotNode); // Add all changes and parent nodes foreach (Guid nodeId in changeSet.Nodes.EnumerateNodes()) { AddNodeToAppendableTreeRecursive(delta, nodeId, nodeMapping, nodeStates, changeSet); } // Prepare list of unreferenced node IDs Dictionary <Guid, Collection <Guid> > references = new Dictionary <Guid, Collection <Guid> >(); // Resolve edges based on mapping foreach (Guid nodeId in delta.EnumerateNodes()) { references.Add(nodeId, new Collection <Guid>()); } // Resolve edges based on mapping foreach (Guid nodeId in delta.EnumerateNodes()) { var node = delta.GetNode(nodeId, NodeAccess.ReadWrite); foreach (var edge in node.Edges.Values) { // Permanent edges should not be touched if (!Utils.IsPermanentEdge(edge)) { // Reroute based on node mapping if (nodeMapping.ContainsKey(edge.ToNodeId)) { edge.ToNodeId = nodeMapping[edge.ToNodeId]; } else { if (!reusedNodes.ContainsKey(edge.ToNodeId)) { reusedNodes.Add(edge.ToNodeId, null); } } // Change edge data if it points to changed node if (edge.Data.Data != null && edge.Data.Data is Guid) { if (nodeMapping.ContainsKey((Guid)edge.Data.Data)) { edge.Data = new EdgeData(edge.Data.Semantic, nodeMapping[(Guid)edge.Data.Data]); } } } else { if (!reusedNodes.ContainsKey(edge.ToNodeId)) { reusedNodes.Add(edge.ToNodeId, null); } } if (references.ContainsKey(edge.ToNodeId)) { references[edge.ToNodeId].Add(nodeId); } } } // Remove remaining unreferenced nodes bool removed = false; do { removed = false; foreach (Guid key in delta.EnumerateNodes()) { // There are no references to the key if ((key != newSnapshotId) && (references[key].Count == 0)) { #if DEBUG Debug.WriteLine("Removed unreferenced key " + key); #endif delta.Remove(key); reusedNodes.Remove(key); nodeStates.Remove(key); foreach (Guid otherKey in references.Keys) { references[otherKey].Remove(key); } foreach (var mappingKey in nodeMapping.Keys) { if (nodeMapping[mappingKey] == key) { nodeMapping.Remove(mappingKey); break; } } removed = true; break; } } }while (removed); // Isolated provider nodes have been corrupted, perform clear changeSet.Nodes.Clear(); changeSet.NodeStates.Clear(); return(delta); }