Quantcast
Channel: Charlie Holland » Technical Issue
Viewing all articles
Browse latest Browse all 5

SharePoint 2010 bug: The primary lookup field cannot be deleted. There are dependent lookups created on this primary lookup field that should be deleted first.

$
0
0

One of the additions is SharePoint 2010 is the dependent lookup. For us as developers, these are very useful since they allow us to include multiple values from a lookup list while using a single lookup column. To see dependent lookups in action, create a new column of type Lookup and notice the checkboxes. When we select any of these options a dependent lookup is created that is linked to our main lookup. In effect dependent lookups are just like every other lookup field except they are read only and their value is controlled by the field referenced by their PrimaryFieldId.

image

However, there is a problem if we create a custom field type that makes use of dependent lookups. Although MSDN provides a great code sample explaining how to programmatically create dependent lookups, what it doesn’t mention is that if we’re creating a custom field type, there’s a bug in the platform that means we can’t delete them again!

Steps to reproduce

  1. Create a custom field control. Override the OnAdded method to create a dependent lookup.
  2. Create a custom Field Editor and hook it up to the field control.
  3. Deploy the solution and add a new field based on the custom field to a list or library.
  4. Delete the new field.

An SPException is thrown with the description:

The primary lookup field cannot be deleted. There are dependent lookups created on this primary lookup field that should be deleted first.

Why does this problem occur?

Our custom field editor is hosted on the FldEditEx.aspx page and it’s this page that provides the Delete button and handles it’s click event. For most fields, performing a delete is simply a case of calling the Delete method on the referenced field. The code for the Delete method then calls the OnDeleting method on the underlying field and allows for any clean up code. However, as you’ve gathered, for Dependent lookups things work differently. Before the Delete method can be called the dependent lookups must be deleted. So we have a catch 22. We can’t clean up the dependent lookups in the usual manner (ie. using OnDeleting) because we can’t call OnDeleting until we’ve cleaned up the dependent lookups!

To get round this problem, the developers have added some code to FldEditEx (Microsoft.SharePoint.ApplicationPages.FieldEditPage). When the Delete button is clicked, dependent lookups are removed. However, there’s a problem with this. They’re only removed if our field editor is derived from LookupFieldEditor. (Although this MSDN article mentions that deriving an editor from LookupFieldEditor is evil).

So what can we do?

We have three options:

  1. Don’t use dependent lookups – In most cases there are no viable alternatives that don’t involve introducing disproportionate complexity. Plus that would be quitting!
  2. Derive our field editor from LookupFieldEditor – This is unlikely to work. The LookupFieldEditor does all kinds of good stuff that will allow the user to mess about with our custom field in ways that we probably don’t want. Plus Uncle Bill’s boys (and girls!) have specifically frowned upon this idea.
  3. Devise a mechanism to execute some custom code before the call to Delete – now you’re talking!

The solution

In a custom field editor add the following code:

private Delegate _baseClickHandler;
private SPFieldLookup _targetField;

protected override void OnInit(EventArgs e)
{
    if (IsPostBack)
    {
        var fep = Page as FieldEditPage;

        if (fep != null)
        {
            RegisterHandlers(fep);
        }
    }
    base.OnInit(e);
}

void RegisterHandlers(FieldEditPage fep)
{
    var fld = fep.GetType().GetField("BtnDelete", BindingFlags.Instance | BindingFlags.NonPublic);
    if (fld != null)
    {
        var deleteButton = fld.GetValue(fep) as Button;

        if (deleteButton != null)
        {
            var events = ((EventHandlerList)(deleteButton.GetType().GetProperty("Events", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Public).GetValue(deleteButton, null)));
            var fi = deleteButton.GetType().GetField("EventClick", BindingFlags.Static | BindingFlags.NonPublic);
            if (fi != null)
            {
                var key = fi.GetValue(deleteButton);

                _baseClickHandler = events[key];

                events.RemoveHandler(key, _baseClickHandler);

                deleteButton.Click += delete_onClick;
            }
        }
    }
}

private void delete_onClick(object sender, EventArgs e)
{
    if (_targetField != null && !_targetField.IsDependentLookup)
    {
        if (_targetField.ParentList != null)
        {
            var dependencies = _targetField.GetDependentLookupInternalNames();

            foreach (var dependency in dependencies)
            {
                //add any other cleanup here
                _targetField.ParentList.Fields.GetFieldByInternalName(dependency).Delete();
            }
        }
    }

    if (_baseClickHandler != null)
    {
        _baseClickHandler.DynamicInvoke(sender, e);
    }

}

public void InitializeWithField(SPField field)
{
    _targetField = field as SPFieldLookup;

    //the rest of your code here
}

Viewing all articles
Browse latest Browse all 5

Trending Articles