public void TopologicalSort_WithoutCycles3_ShouldSort() { // arrange var a = new Node { Name = "A" }; var b = new Node { Name = "B" }; var c = new Node { Name = "C" }; var d = new Node { Name = "D" }; // act var result = DirectedAcyclicGraphVerifier.TopologicalSort( new[] { a, b, c, d }, node => node.Name, node => node.DependsOn); // assert Assert.IsNotNull(result); Assert.AreEqual(4, result.Count); Assert.AreEqual("A", result[0].Name); Assert.AreEqual("B", result[1].Name); Assert.AreEqual("C", result[2].Name); Assert.AreEqual("D", result[3].Name); }
public void TopologicalSort_WithCycles2_ShouldThrow() { // arrange var a = new Node { Name = "A" }; var b = new Node { Name = "B" }; var c = new Node { Name = "C" }; var d = new Node { Name = "D" }; a.DependsOn = new[] { "B" }; b.DependsOn = new[] { "C" }; c.DependsOn = new[] { "D" }; a.DependsOn = new[] { "A" }; // act && assert Assert.ThrowsException <InvalidOperationException>( () => DirectedAcyclicGraphVerifier.TopologicalSort( new[] { a, b, c, d }, node => node.Name, node => node.DependsOn)); }
private async Task <ImportResult> Import(ImportClassifierList request, CancellationToken cancellationToken) { var errors = new List <string>(); var sortedItems = request.Data.Items != null ? DirectedAcyclicGraphVerifier.TopologicalSort(request.Data.Items, node => node.Code, node => node.ParentCode != null?new[] { node.ParentCode } : null) : null; var sortedGroups = _type.HierarchyType == HierarchyType.Groups && request.Data.Groups != null ? DirectedAcyclicGraphVerifier.TopologicalSort(request.Data.Groups, node => node.Code, node => node.ParentCode != null?new[] { node.ParentCode } : null) : null; using (var scope = _unitOfWorkFactory.Create()) { using (var db = _dbContextFactory.Create()) { var closureTable = new ClosureTableHandler(db, _type); if (sortedItems != null) { foreach (var item in sortedItems) { if (_existingItems.TryGetValue(item.Code, out var dbItem) == false) { var itemUid = Guid.NewGuid(); Guid?parentUid = null; if (_type.HierarchyType == HierarchyType.Items && item.ParentCode != null) { if (_existingItems.TryGetValue(item.ParentCode, out var dbParentItem)) { parentUid = dbParentItem.Uid; } else { errors.Add($"Item {item.ParentCode} specified as parent for item {item.Code} not found in classifier {_type.Code}."); } } await db.GetTable <DbClassifier>() .Value(x => x.Uid, itemUid) .Value(x => x.TypeUid, _type.Uid) .Value(x => x.StatusCode, item.StatusCode) .Value(x => x.Code, item.Code) .Value(x => x.Name, item.Name) .Value(x => x.ParentUid, parentUid) .InsertAsync(cancellationToken); // todo: check result and throw error await closureTable.Insert(itemUid, parentUid, cancellationToken); _existingItems.Add(item.Code, new DbClassifier { Uid = itemUid, TypeUid = _type.Uid, StatusCode = item.StatusCode, Code = item.Code, Name = item.Name, ParentUid = parentUid }); } else { var stmt = db.GetTable <DbClassifier>() .Where(x => x.Uid == dbItem.Uid) .AsUpdatable(); var changed = false; if (dbItem.Name != item.Name) { stmt = stmt.Set(x => x.Name, item.Name); changed = true; } if (dbItem.StatusCode != item.StatusCode) { stmt = stmt.Set(x => x.StatusCode, item.StatusCode); changed = true; } if (changed) { await stmt.UpdateAsync(cancellationToken); } } } } if (_type.HierarchyType == HierarchyType.Groups) { if (sortedGroups != null) { foreach (var group in sortedGroups) { if (_existingGroups.TryGetValue(group.Code, out var dbGroup) == false) { Guid?parentUid = null; if (group.ParentCode != null) { if (_existingGroups.TryGetValue(group.ParentCode, out var parentGroup)) { parentUid = parentGroup.Uid; } else { // todo: validate all before import or throw error errors.Add($"Group {group.ParentCode} specified as parent for group {group.Code} not found in classifier {_type.Code}."); } } var groupUid = Guid.NewGuid(); await db.GetTable <DbClassifierGroup>() .Value(x => x.Uid, groupUid) .Value(x => x.TreeUid, _tree.Uid) .Value(x => x.Code, group.Code) .Value(x => x.Name, group.Name) .Value(x => x.ParentUid, parentUid) .InsertAsync(cancellationToken); // todo: check result and throw error await closureTable.Insert(groupUid, parentUid, cancellationToken); _existingGroups.Add(group.Code, new DbClassifierGroup { Uid = groupUid, TreeUid = _tree.Uid, Code = group.Code, Name = group.Name, ParentUid = parentUid }); } else { var stmt = db.GetTable <DbClassifierGroup>() .Where(x => x.Uid == dbGroup.Uid) .AsUpdatable(); var changed = false; if (dbGroup.Name != group.Name) { stmt = stmt.Set(x => x.Name, group.Name); changed = true; } if (changed) { await stmt.UpdateAsync(cancellationToken); } } } } if (request.Data.Links != null) { var existingLinks = new HashSet <Tuple <Guid, Guid> >( (from link in db.GetTable <DbClassifierLink>() join g in db.GetTable <DbClassifierGroup>() on link.GroupUid equals g.Uid join i in db.GetTable <DbClassifier>() on link.ItemUid equals i.Uid where i.TypeUid == _type.Uid && g.TreeUid == _tree.Uid select new { link.GroupUid, link.ItemUid }) .Select(x => Tuple.Create(x.GroupUid, x.ItemUid))); foreach (var itemInGroup in request.Data.Links) { if (_existingGroups.TryGetValue(itemInGroup.GroupCode, out var dbGroup) == false) { errors.Add($"Group {itemInGroup.GroupCode} specified in link for item {itemInGroup.ItemCode} not found in classifier {_type.Code}."); } if (_existingItems.TryGetValue(itemInGroup.ItemCode, out var dbItem) == false) { errors.Add($"Item {itemInGroup.ItemCode} specified in link for group {itemInGroup.GroupCode} not found in classifier {_type.Code}."); } var link = Tuple.Create(dbGroup.Uid, dbItem.Uid); if (existingLinks.Contains(link) == false) { await db.GetTable <DbClassifierLink>() .Value(x => x.GroupUid, dbGroup.Uid) .Value(x => x.ItemUid, dbItem.Uid) .InsertAsync(cancellationToken); existingLinks.Add(link); } } } } } scope.Commit(); } return(new ImportResult { Errors = errors }); }
private ImportResult BulkImport(ImportClassifierList request) { var activeItems = new Dictionary <string, DbClassifier>(); var inactiveItems = new Dictionary <string, DbClassifier>(); var groups = new Dictionary <string, DbClassifierGroup>(); var links = new List <DbClassifierLink>(); var closures = new ClosureMap(); if (request.Data.Items != null) { var sortedItems = DirectedAcyclicGraphVerifier.TopologicalSort( request.Data.Items, node => node.Code, node => node.ParentCode != null ? new[] { node.ParentCode } : null); foreach (var item in sortedItems) { var itemUid = Guid.NewGuid(); Guid?parentUid = null; if (_type.HierarchyType == HierarchyType.Items) { if (item.ParentCode != null) { if (activeItems.TryGetValue(item.ParentCode, out var parent)) { parentUid = parent.Uid; } else if (inactiveItems.TryGetValue(item.ParentCode, out parent)) { parentUid = parent.Uid; } } closures.Insert(itemUid, parentUid); } var items = item.StatusCode == ClassifierStatusCode.Active ? activeItems : inactiveItems; items[item.Code] = new DbClassifier { Uid = itemUid, TypeUid = _type.Uid, StatusCode = item.StatusCode, Code = item.Code, Name = item.Name, ParentUid = parentUid }; } } if (_type.HierarchyType == HierarchyType.Groups) { if (request.Data.Groups != null) { var sortedGroups = DirectedAcyclicGraphVerifier.TopologicalSort( request.Data.Groups, node => node.Code, node => node.ParentCode != null ? new[] { node.ParentCode } : null); foreach (var group in sortedGroups) { var groupUid = Guid.NewGuid(); Guid?parentUid = null; if (group.ParentCode != null) { parentUid = groups[group.ParentCode].Uid; } groups[group.Code] = new DbClassifierGroup { Uid = groupUid, TreeUid = _tree.Uid, Code = group.Code, Name = group.Name, ParentUid = parentUid }; closures.Insert(groupUid, parentUid); } } // todo: link all unlinked items to root if (request.Data.Links != null) { foreach (var link in request.Data.Links) { var items = link.ItemStatusCode == ClassifierStatusCode.Active ? activeItems : inactiveItems; links.Add(new DbClassifierLink { GroupUid = groups[link.GroupCode].Uid, ItemUid = items[link.ItemCode].Uid }); } } } using (var scope = _unitOfWorkFactory.Create()) { using (var db = _dbContextFactory.Create()) { var copyOptions = new BulkCopyOptions { BulkCopyType = BulkCopyType.ProviderSpecific }; // todo: move heavy operations outside of open connection if (activeItems.Values.Count > 0 || inactiveItems.Values.Count > 0) { var sorted = DirectedAcyclicGraphVerifier.TopologicalSort( activeItems.Values.Union(inactiveItems.Values).ToList(), node => node.Uid, node => node.ParentUid != null ? new[] { node.ParentUid } : null); db.BulkCopy(copyOptions, sorted); } if (groups.Values.Count > 0) { var sorted = DirectedAcyclicGraphVerifier.TopologicalSort( groups.Values, node => node.Uid, node => node.ParentUid != null ? new[] { node.ParentUid } : null); db.BulkCopy(copyOptions, sorted); } if (links.Count > 0) { db.BulkCopy(copyOptions, links); } db.BulkCopy(copyOptions, closures.GetAll()); } scope.Commit(); } return(new ImportResult()); }