public void TestGraphSccAlgorithm() { //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 Graph(); 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(Graph 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); }
//Sequences set of records in a looped non-trivial entity group. Assigns record.SortSubIndex value after sequencing private void SequenceSubGroup(IEnumerable<EntityRecord> records) { var graph = new Graph(); 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; // finally, the target record's table 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 TopologicalIndex bool hasNonTrivialGroups = false; foreach (var v in graph.Vertexes) { var rec = (EntityRecord)v.Tag; rec.SortSubIndex = rec.Status == EntityStatus.New ? -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 = StringHelper.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; } }