문제

I recently needed to update over a hundred Concept items using a script utilizing Colectica SDK for DDI, and I need to propagate those updates throughout all locations within a set with a known root Project item. This means that each item referencing an older version of a Concept will need to be updated to reference the new version. This will create a new version of the refrencing item which will need its updates propagated within the set similarly.

My best attempted solution uses an implementation of IVersionableVisitor to update out-of-date references, marking items dirty as needed. At this point, a DirtyItemGatherer visitor can be used to collect and publish items. This would not work in a single pass, but the hope was that it could be run until no more items needed to be updated.

internal class ReferencedVersionUpdaterVisitor : IVersionableVisitor
{
    public ReferencedVersionUpdaterVisitor(WcfRepositoryClient repositoryClient)
    {
        this.RepositoryClient = repositoryClient;
        this.VisitedLog = new Dictionary<Tuple<Guid, string>,bool>();
        this.Context = new List<IVersionable>();
    }

    private WcfRepositoryClient RepositoryClient { get; set; }

    private Dictionary<Tuple<Guid, string>, bool> VisitedLog { get; set; }

    public bool HaveVisited(IdentifierTriple id)
    {
        var logId = Tuple.Create(id.Identifier, id.AgencyId);
        return this.VisitedLog.ContainsKey(logId) && this.VisitedLog[logId];
    }

    public void RegisterVisit(IdentifierTriple id)
    {
        this.VisitedLog[Tuple.Create(id.Identifier, id.AgencyId)] = true;
    }

    private List<IVersionable> Context { get; set; }

    public void BeginVisitItem(IVersionable item)
    {
        if (!this.HaveVisited(item.CompositeId) &&
            !this.Context.Any(contextItem => contextItem.CompositeId.Identifier.Equals(item.CompositeId.Identifier) && contextItem.CompositeId.AgencyId.Equals(item.CompositeId.AgencyId)))
        {
            if (!item.IsPopulated)
            {
                var previousVersion = item.Version;
                this.RepositoryClient.PopulateItem(item, true, ChildReferenceProcessing.Instantiate);
                if (previousVersion != item.Version)
                {
                    item.IsDirty = true;
                }
            }
            this.Context.Add(item);
        }
    }

    public void EndVisitItem(IVersionable item)
    {
        if (!this.HaveVisited(item.CompositeId))
        {
            this.Context.Remove(item);
            this.RegisterVisit(item.CompositeId);
        }
    }
}

Unfortunately it does not work; every pass finds the same items because the set still contains parent items referencing the old versions of the children. I've put thought and effort into ideas for tuning this approach, such as modifying items in EndVisitItem() to modify on the way up, but it doesn't address the real issue.

I think that the base issue is that the visitor needs to modify parent items as it walks the set. Since this is a graph, there is not really a parent so much as a referencing item that I happened to use to get to the current node. I attempted to record this using the Context property, but its contents don't seem to always match my expectation that the last item in the context list is the "parent" referencing item.

In Colectica Designer, this problem is solved by internal use of the Navigator structure, which I unfortunately do not see available in the Colectica SDK. Also, while Colectica Designer's Navigator-based solution performs very well, the performance of this visitor is quite poor because of the multiple round-trips to the Colectica Repository from BeginVisitItem(). This makes me feel as though I might be going about this all wrong. Is there a better way to approach this using the tools available through Colectica SDK?

I should also note that I am aware of the ability to populate latest on all child items, but from there, the problem of locating all dirty items, bumping their versions, saving them, and propagating the updates seems to turn into the same problem that I'm already trying to solve.

I would also like to add that a side-effect of updating everything within the rooted set to the latest versions, not just those Concepts and related items, would be perfectly fine, and, in fact, may be preferred.

도움이 되었습니까?

해결책

Here's the solution that I eventually came up with.

Begin by getting root project item using the InstantiateLatest option.

var testProject = repositoryClient.GetLatestItem(
    itemGuid,
    agencyId,
    ChildReferenceProcessing.InstantiateLatest);

Next send a MarkUpdatedReferencesDirtyVisitor to the root item (code for this visitor is included at the end of this answer).

var markUpdatedReferencesDirtyVisitor =
    new MarkUpdatedReferencesDirtyVisitor(repositoryClient);
testProject.Accept(markUpdatedReferencesDirtyVisitor);

Follow this by sending a DirtyItemGatherer visitor without the option to gather non-dirty items but with the option to mark as dirty parents of dirty items.

var dirtyItemGatherer = new DirtyItemGatherer(false, true);
testProject.Accept(dirtyItemGatherer);

At this point, it's a matter of doing some basic boilerplate stuff, such as bumping version numbers for dirty items and doing sanity-checks, before making a call to WcfRepositoryClient.RegisterItems() to publish the updates.

Here is the code for MarkUpdatedReferencesDirtyVisitor. The basic idea is to grab the latest version of each item's children and compare them to the current child versions. If there is a difference, mark the item as dirty. Later, the version with the latest children will be published.

internal class MarkUpdatedReferencesDirtyVisitor : VersionableVisitorBase
{
    public MarkUpdatedReferencesDirtyVisitor(
        WcfRepositoryClient repositoryClient)
    {
        this.RepositoryClient = repositoryClient;
    }

    private WcfRepositoryClient RepositoryClient { get; set; }

    public override void BeginVisitItem(IVersionable item)
    {
        if (!this.HaveVisited(item.CompositeId) && !item.IsDirty)
        {
            base.BeginVisitItem(item);
            if (!item.IsPopulated)
            {
                this.RepositoryClient.PopulateItem(
                    item,
                    true,
                    ChildReferenceProcessing.InstantiateLatest);
            }
            var latestChildren = item.GetChildren();
            var currentChildren = this.RepositoryClient.GetItem(
                    item.CompositeId,
                    ChildReferenceProcessing.Instantiate).GetChildren();
            if (latestChildren.Count != currentChildren.Count)
            {
                item.IsDirty = true;
            }
            else
            {
                for(int i = 0; i < currentChildren.Count; i++)
                {
                    if (!latestChildren[i].CompositeId.Equals(
                        currentChildren[i].CompositeId))
                    {
                        item.IsDirty = true;
                        break;
                    }
                }
            }
        }
    }
}

This is not as efficient as I would have liked, but it does work in a single pass, so the performance is acceptable.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top