public void Batch_command_throws_on_commands_with_circular_dependencies() { var model = CreateCyclicFKModel(); var configuration = CreateContextServices(model); var stateManager = configuration.GetRequiredService <IStateManager>(); var fakeEntry = stateManager.GetOrCreateEntry(new FakeEntity { Id = 42, RelatedId = 1 }); fakeEntry.SetEntityState(EntityState.Added); var relatedFakeEntry = stateManager.GetOrCreateEntry(new RelatedFakeEntity { Id = 1, RelatedId = 42 }); relatedFakeEntry.SetEntityState(EntityState.Added); Assert.Equal( CoreStrings.CircularDependency( string.Join(", ", model.GetEntityType(typeof(RelatedFakeEntity)).GetForeignKeys().First(), model.GetEntityType(typeof(FakeEntity)).GetForeignKeys().First())), Assert.Throws <InvalidOperationException>( () => { var commandBatches = CreateCommandBatchPreparer().BatchCommands(new[] { fakeEntry, relatedFakeEntry }, new DbContextOptions <DbContext>()).ToArray(); }).Message); }
private IProperty VerifyRootPrincipal( IProperty principalProperty, Dictionary <IProperty, IProperty> verifiedProperties, ImmutableList <IForeignKey> visitedForeignKeys, out string errorMessage) { errorMessage = null; IProperty rootPrincipal; if (verifiedProperties.TryGetValue(principalProperty, out rootPrincipal)) { return(rootPrincipal); } var rootPrincipals = new Dictionary <IProperty, IForeignKey>(); foreach (var foreignKey in principalProperty.DeclaringEntityType.GetForeignKeys()) { for (var index = 0; index < foreignKey.Properties.Count; index++) { if (principalProperty == foreignKey.Properties[index]) { var nextPrincipalProperty = foreignKey.PrincipalKey.Properties[index]; if (visitedForeignKeys.Contains(foreignKey)) { var cycleStart = visitedForeignKeys.IndexOf(foreignKey); var cycle = visitedForeignKeys.GetRange(cycleStart, visitedForeignKeys.Count - cycleStart); errorMessage = Strings.CircularDependency(cycle.Select(fk => fk.ToString()).Join()); continue; } rootPrincipal = VerifyRootPrincipal(nextPrincipalProperty, verifiedProperties, visitedForeignKeys.Add(foreignKey), out errorMessage); if (rootPrincipal == null) { if (principalProperty.RequiresValueGenerator) { rootPrincipals[principalProperty] = foreignKey; } continue; } if (principalProperty.RequiresValueGenerator) { ShowError(Strings.ForeignKeyValueGenerationOnAdd( principalProperty.Name, principalProperty.DeclaringEntityType.DisplayName(), Property.Format(foreignKey.Properties))); return(principalProperty); } rootPrincipals[rootPrincipal] = foreignKey; } } } if (rootPrincipals.Count == 0) { if (errorMessage != null) { return(null); } if (!principalProperty.RequiresValueGenerator) { ShowError(Strings.PrincipalKeyNoValueGenerationOnAdd(principalProperty.Name, principalProperty.DeclaringEntityType.DisplayName())); return(null); } return(principalProperty); } if (rootPrincipals.Count > 1) { var firstRoot = rootPrincipals.Keys.ElementAt(0); var secondRoot = rootPrincipals.Keys.ElementAt(1); ShowWarning(Strings.MultipleRootPrincipals( rootPrincipals[firstRoot].DeclaringEntityType.DisplayName(), Property.Format(rootPrincipals[firstRoot].Properties), firstRoot.DeclaringEntityType.DisplayName(), firstRoot.Name, Property.Format(rootPrincipals[secondRoot].Properties), secondRoot.DeclaringEntityType.DisplayName(), secondRoot.Name)); return(firstRoot); } errorMessage = null; rootPrincipal = rootPrincipals.Keys.Single(); verifiedProperties[principalProperty] = rootPrincipal; return(rootPrincipal); }
public virtual IReadOnlyList <TVertex> TopologicalSort( [CanBeNull] Func <TVertex, TVertex, IEnumerable <TEdge>, bool> canBreakEdge, [CanBeNull] Func <IEnumerable <Tuple <TVertex, TVertex, IEnumerable <TEdge> > >, string> formatCycle) { var sortedQueue = new List <TVertex>(); var predecessorCounts = new Dictionary <TVertex, int>(); foreach (var vertex in _vertices) { var count = GetIncomingNeighbours(vertex).Count(); if (count == 0) { // Collect verticies without predecessors sortedQueue.Add(vertex); } else { // Track number of predecessors for remaining verticies predecessorCounts[vertex] = count; } } var index = 0; while (sortedQueue.Count < _vertices.Count) { while (index < sortedQueue.Count) { var currentRoot = sortedQueue[index]; foreach (var successor in GetOutgoingNeighbours(currentRoot).Where(neighbour => predecessorCounts.ContainsKey(neighbour))) { // Decrement counts for edges from sorted verticies and append any verticies that no longer have predecessors predecessorCounts[successor]--; if (predecessorCounts[successor] == 0) { sortedQueue.Add(successor); predecessorCounts.Remove(successor); } } index++; } // Cycle breaking if (sortedQueue.Count < _vertices.Count) { var broken = false; var candidateVertices = predecessorCounts.Keys.ToList(); var candidateIndex = 0; // Iterrate over the unsorted verticies while (candidateIndex < candidateVertices.Count && !broken && canBreakEdge != null) { var candidateVertex = candidateVertices[candidateIndex]; // Find verticies in the unsorted portion of the graph that have edges to the candidate var incommingNeighbours = GetIncomingNeighbours(candidateVertex) .Where(neighbour => predecessorCounts.ContainsKey(neighbour)).ToList(); foreach (var incommingNeighbour in incommingNeighbours) { // Check to see if the edge can be broken if (canBreakEdge(incommingNeighbour, candidateVertex, _successorMap[incommingNeighbour][candidateVertex])) { predecessorCounts[candidateVertex]--; if (predecessorCounts[candidateVertex] == 0) { sortedQueue.Add(candidateVertex); predecessorCounts.Remove(candidateVertex); broken = true; break; } } } candidateIndex++; } if (!broken) { // Failed to break the cycle var currentCycleVertex = predecessorCounts.First().Key; var cycle = new List <TVertex>(); cycle.Add(currentCycleVertex); var finished = false; while (!finished) { // Find a cycle foreach (var predecessor in GetIncomingNeighbours(currentCycleVertex) .Where(neighbour => predecessorCounts.ContainsKey(neighbour))) { if (predecessorCounts[predecessor] != 0) { predecessorCounts[currentCycleVertex] = -1; currentCycleVertex = predecessor; cycle.Add(currentCycleVertex); finished = predecessorCounts[predecessor] == -1; break; } } } cycle.Reverse(); // Throw an exception if (formatCycle == null) { throw new InvalidOperationException( Strings.CircularDependency( cycle.Select(vertex => vertex.ToString()).Join(" -> "))); } // Build the cycle message data currentCycleVertex = cycle.First(); var cycleData = new List <Tuple <TVertex, TVertex, IEnumerable <TEdge> > >(); foreach (var vertex in cycle.Skip(1)) { cycleData.Add(Tuple.Create(currentCycleVertex, vertex, GetEdges(currentCycleVertex, vertex))); currentCycleVertex = vertex; } throw new InvalidOperationException( Strings.CircularDependency( formatCycle(cycleData))); } } } return(sortedQueue); }
public virtual IReadOnlyList <List <TVertex> > BatchingTopologicalSort( [CanBeNull] Func <IEnumerable <Tuple <TVertex, TVertex, IEnumerable <TEdge> > >, string> formatCycle) { var currentRootsQueue = new List <TVertex>(); var predecessorCounts = new Dictionary <TVertex, int>(); foreach (var vertex in _vertices) { var count = GetIncomingNeighbours(vertex).Count(); if (count == 0) { // Collect verticies without predecessors currentRootsQueue.Add(vertex); } else { // Track number of predecessors for remaining verticies predecessorCounts[vertex] = count; } } var result = new List <List <TVertex> >(); var nextRootsQueue = new List <TVertex>(); var currentRootIndex = 0; while (currentRootIndex < currentRootsQueue.Count) { var currentRoot = currentRootsQueue[currentRootIndex]; currentRootIndex++; // Remove edges from current root and add any exposed vertices to the next batch foreach (var successor in GetOutgoingNeighbours(currentRoot)) { predecessorCounts[successor]--; if (predecessorCounts[successor] == 0) { nextRootsQueue.Add(successor); } } // Roll lists over for next batch if (currentRootIndex == currentRootsQueue.Count) { result.Add(currentRootsQueue); currentRootsQueue = nextRootsQueue; currentRootIndex = 0; if (currentRootsQueue.Count != 0) { nextRootsQueue = new List <TVertex>(); } } } if (result.Sum(b => b.Count) != _vertices.Count) { // TODO: Support cycle-breaking? var currentCycleVertex = predecessorCounts.First(p => p.Value != 0).Key; var cycle = new List <TVertex>(); cycle.Add(currentCycleVertex); var finished = false; while (!finished) { foreach (var predecessor in GetIncomingNeighbours(currentCycleVertex) .Where(neighbour => predecessorCounts.ContainsKey(neighbour))) { if (predecessorCounts[predecessor] != 0) { predecessorCounts[currentCycleVertex] = -1; currentCycleVertex = predecessor; cycle.Add(currentCycleVertex); finished = predecessorCounts[predecessor] == -1; break; } } } cycle.Reverse(); // Throw an exception if (formatCycle == null) { throw new InvalidOperationException( Strings.CircularDependency( cycle.Select(vertex => vertex.ToString()).Join(" -> "))); } // Build the cycle message data currentCycleVertex = cycle.First(); var cycleData = new List <Tuple <TVertex, TVertex, IEnumerable <TEdge> > >(); foreach (var vertex in cycle.Skip(1)) { cycleData.Add(Tuple.Create(currentCycleVertex, vertex, GetEdges(currentCycleVertex, vertex))); currentCycleVertex = vertex; } throw new InvalidOperationException( Strings.CircularDependency( formatCycle(cycleData))); } return(result); }