// Note about special case: members with CascadeDelete attribute. // Demo case setup. 3 entities, IBook, IAuthor, and IBookAuthor as link table; IBookAuthor references IBook with CascadeDelete, // and references IAuthor without cascade. // Because of CascadeDelete, when we delete IBook and IBookAuthor in one operation, the order of IBook vs IBookAuthor does not matter: // even if IBook comes before IBookAuthor, delete will succeed because of cascade delete of IBookAuthor. // The problem case is when we are deleting IBook and IAuthor, without explicitly deleting IBookAuthor. // In this case IAuthor should be deleted after IBook - otherwise still existing IBookAuthor record // would prevent it from deleting. As there's no explicit IBookAuthor in delete set, and there's // no FK links between IAuthor and IBook - then they may come to delete in any order, and trans might fail. // The solution is to introduce an extra direct link between IBook and IAuthor in abstract SCC node tree. // This extra link will ensure proper topological ordering of IBook and IAuthor. // Note that we still need to add link between IBookAuthor and IBook - for proper ordering of inserts. private void ComputeTopologicalIndexes() { // Run SCC algorithm var g = new SccGraph(); //Perform SCC analysis. foreach (var ent in Model.Entities) { ent.SccVertex = g.Add(ent); } //setup links foreach (var ent in Model.Entities) { var cascadeMembers = new List <EntityMemberInfo>(); var nonCascadeMembers = new List <EntityMemberInfo>(); foreach (var member in ent.RefMembers) { var targetEnt = member.ReferenceInfo.ToKey.Entity; ent.SccVertex.AddLink(targetEnt.SccVertex); if (member.Flags.IsSet(EntityMemberFlags.CascadeDelete)) { cascadeMembers.Add(member); } else { nonCascadeMembers.Add(member); } }//foreach member //For all cascade member (IBookAuthor.Author) targets add direct links to all non-cascade member targets // (from IBook to IAuthor) foreach (var cascMember in cascadeMembers) { var cascTarget = cascMember.ReferenceInfo.ToKey.Entity; foreach (var nonCascMember in nonCascadeMembers) { var nonCascTarget = nonCascMember.ReferenceInfo.ToKey.Entity; cascTarget.SccVertex.AddLink(nonCascTarget.SccVertex); } } //foreach cascMember } //foreach ent //Build SCC var sccCount = g.BuildScc(); //read scc index and clear vertex fields foreach (var ent in Model.Entities) { var v = ent.SccVertex; ent.TopologicalIndex = v.SccIndex; if (v.NonTrivialGroup) { ent.Flags |= EntityFlags.TopologicalGroupNonTrivial; } ent.SccVertex = null; } }
public void TestHelpers_GraphSccAlgorithm() { //This test uses a simple graph from the wikipedia page about SCC: http://en.wikipedia.org/wiki/Strongly_connected_components // The copy of this image is in SccTestGraph.jpg file in this test project. var expected = "a, Scc=1; b, Scc=1; e, Scc=1; c, Scc=2; d, Scc=2; h, Scc=2; f, Scc=3; g, Scc=3"; //expected SCC indexes var gr = new SccGraph(); SetupSampleGraph(gr); gr.BuildScc(); // additionally sort by tags, so that result string matches var sortedVertexes = gr.Vertexes.OrderBy(v => v.SccIndex).ThenBy(v => (string)v.Tag).ToList(); var strOut = string.Join("; ", sortedVertexes); Assert.AreEqual(expected, strOut, "SCC computation did not return expected result."); }
// Builds a sample graph from http://en.wikipedia.org/wiki/Strongly_connected_components private static void SetupSampleGraph(SccGraph gr) { var a = gr.Add("a"); var b = gr.Add("b"); var c = gr.Add("c"); var d = gr.Add("d"); var e = gr.Add("e"); var f = gr.Add("f"); var g = gr.Add("g"); var h = gr.Add("h"); //links a.AddLink(b); b.AddLink(e, f, c); c.AddLink(d, g); d.AddLink(c, h); e.AddLink(a, f); f.AddLink(g); g.AddLink(f); h.AddLink(g, d); }
public void Solve(ConsoleReader cr, ConsoleWriter cw) { int n = cr; int m = cr; var g = new SccGraph(n); for (int i = 0; i < m; i++) { int u = cr; int v = cr; g.AddEdge(u, v); } var scc = g.SCC(); cw.WriteLine(scc.Length); foreach (var v in scc) { cw.StreamWriter.Write(v.Length); cw.StreamWriter.Write(' '); cw.WriteLineJoin(v); } }
//Sequence set of records in a looped non-trivial entity group. Assigns record.SortSubIndex value as a result of sequencing private static void AssignSubIndexes(IList <EntityRecord> records) { var graph = new SccGraph(); foreach (var rec in records) { var ent = rec.EntityInfo; foreach (var refMember in ent.RefMembers) { var targetEnt = rec.GetValue(refMember); //If reference is not modified or not set, then nothing to do if (targetEnt == null) { continue; } var targetRec = EntityHelper.GetRecord(targetEnt); // we are interested only in case when both records are inserted, or both are deleted. if (targetRec.Status != rec.Status) { continue; } // Both records must be in the same SCC group - i.e. have the same SccIndex if (targetRec.EntityInfo.TopologicalIndex != rec.EntityInfo.TopologicalIndex) { continue; } // We have potential conflict; add vertexes and link for the conflict var thisV = graph.FindOrAdd(rec); var targetV = graph.FindOrAdd(targetRec); thisV.AddLink(targetV); } }//foreach cmd //Check if any conflicts found if (graph.Vertexes.Count == 0) { return; } //Build SCC graph graph.BuildScc(); // Once SCC is built, we have SCC indexes in Vertexes; use them to assign Record's SortSubIndex bool hasNonTrivialGroups = false; foreach (var v in graph.Vertexes) { var rec = (EntityRecord)v.Tag; rec.SortSubIndex = rec.Status == EntityStatus.New ? v.SccIndex : -v.SccIndex; // -v.SccIndex : v.SccIndex; hasNonTrivialGroups |= v.NonTrivialGroup; } //if there are non-trivial groups, it means we have circular references in the set. if (hasNonTrivialGroups) { var entList = string.Join(",", records.Select(r => r.PrimaryKey.ToString())); var msg = Util.SafeFormat("Detected circular references between entities in an update set. Cannot commit group update. Entities: [{0}].", entList); var fault = new ClientFault() { Code = ClientFaultCodes.CircularEntityReference, Message = msg }; var faultEx = new ClientFaultException(new[] { fault }); faultEx.LogAsError = true; throw faultEx; } }