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));
        }
Esempio n. 3
0
        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
            });
        }
Esempio n. 4
0
        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());
        }