modifying sitecore itemlinks

I recently had to modify an archive functionality in which you could archive an item or one of it’s children. If you archived child A then it’s parent would be cloned or copied (depending on the situation) to the archive and the child would be moved. But we also have other items that were referencing this item and it would also be possible to archive just one language version. I won’t go into the rather complex rules of the archiving command, but basically I needed to duplicate all references to the child item and link them to the archived item.

I poked around the web a bit and the sitecore experts blog sent me in the right direction.

I used their GetReferrers method to retrieve an IEnumerable of ItemLinks to the original item.

1
2
3
4
5
6
7
8
private IEnumerable<ItemLink> GetReferrers(Item item, ID sourceFieldId = null)
{

ItemLink[] referrers = sourceFieldId != null as ID
? Globals.LinkDatabase.GetReferrers(item, sourceFieldId)
: Globals.LinkDatabase.GetReferrers(item);

return referrers;
}

After that it is just a matter of looping through the item links and adding a new link to the source item. In case of unarchiving I needed the opposite method to delete some item links, so I added that as well.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
private void AddLinks(Item source, Item target)
{

IEnumerable<ItemLink> referencingItems = GetReferrers(source, Constants.FieldId1);
referencingItems.Union(GetReferrers(source, Constants.FieldId2));
List<ItemLink> links = referencingItems.ToList();

if (links == null || !links.Any())
{
return;
}

foreach (ItemLink itemLink in links)
{
if (itemLink.SourceFieldID != Guid.Empty.ToID())
{
Item linkSource = itemLink.GetSourceItem();
linkSource.Editing.BeginEdit();
string fieldName = linkSource.Fields.FirstOrDefault(f => f.ID == itemLink.SourceFieldID).Name;
((MultilistField)linkSource.Fields[fieldName]).Add(target.ID.ToString());
linkSource.Editing.AcceptChanges();
linkSource.Editing.EndEdit();
}
}
}

private void DeleteLinks(Item item)
{

IEnumerable<ItemLink> referencingItems = GetReferrers(item, Constants.FieldId1);
referencingItems.Union(GetReferrers(item, Constants.FieldId2));
List<ItemLink> links = referencingItems.ToList();

if (links == null || !links.Any())
{
return;
}

foreach (ItemLink itemLink in links)
{
Item source = itemLink.GetSourceItem();
source.Editing.BeginEdit();
MultilistField field = source.Fields.FirstOrDefault(f => f.ID == itemLink.SourceFieldID);
if (field.Contains(item.ID.ToString()))
{
field.Remove(item.ID.ToString());
}
source.Editing.AcceptChanges();
source.Editing.EndEdit();
}
}

Seeing as both these methods are almost identical, some refactoring is required. It turns out this pattern has a name, the “hole in the middle pattern”. I personally would have called it the donut (doughnut) pattern.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private void ModifyLinks(Action<MultilistField, Item> action, Item sourceItem, Item targetItem = null)
{

IEnumerable<ItemLink> referencingItems = GetReferrers(sourceItem, IDownloadConstants.ProductsFieldId);
referencingItems.Union(GetReferrers(sourceItem, ISoftware_InformationConstants.ProductsFieldId));
List<ItemLink> links = referencingItems.ToList();

if (links == null || !links.Any())
{
return;
}

foreach (ItemLink itemLink in links)
{
if (itemLink.SourceFieldID != Guid.Empty.ToID())
{
Item source = itemLink.GetSourceItem();
source.Editing.BeginEdit();
MultilistField field = source.Fields.FirstOrDefault(f => f.ID == itemLink.SourceFieldID);

action(field, targetItem);

source.Editing.AcceptChanges();
source.Editing.EndEdit();
}
}
}

Delete links like in the following snippet, where you pass the source item twice.

1
2
3
4
5
6
ModifyLinks((field, target) => {
if (field.Contains(target.ID.ToString()))
{
field.Remove(target.ID.ToString());
}
}, item, item);

Add links in a similar fashion by passing in the source item and the target item. The first being the one you want to copy the links from and the latter being the one you want a new link to.

1
ModifyLinks((field, target) => {field.Add(target.ID.ToString());}, item, addedItem);