Item has already been added. Key in dictionary: ...

Wednesday, April 11, 2007 10:10:58 AM

Recently I had the need to create a GridView that would call a stored procedure with a single character as a parameter.  This was used to implement custom alphabetized paging.  The character value was derived from the QueryString after being sanitized and verified to be an appropriate value.  Sounds pretty simple so far, right?

While setting up a GridView in ASP.NET 2.0 I came across the Item has already been added. error message when performing a sort.

The DataSource that I used was the ObjectDataSource.  As with most of my projects, I create a Data Abstraction Layer, or DAL, to abstract all of my data access and give me more standardized methods to perform all of my database work.  This process is well document on several websites, so I will not get into the details here.  It is a DataSet created from within Visual Studio 2005, the back-end is SQL Express 2005 and consists of 2 tables, 8 total columns between them, a view and roughly 5 stored procedures per table to handle the common database functions: Get Records, Get Record By ID, Update Record, Add Record, Delete Record.

To start off I just set a default value on the ODS of " " to display everything. The GridView loaded the data from the DataSource using the default value, displayed it, and life was good.  Just as I began to reach for a cookie for my efforts, I decided to sort it.  That is when everything changed.  I received the dreaded: Item has already been added. Key in dictionary:  error message.

As usual, I Googled away for an hour and found no results to solve this problem.  I began throwing in my Debug.WriteLine statements everywhere I could.  GridView1_DataBinding, GridView1_DataBound, ObjectDataSource1_DataBinding.  You name it, I wrote it.  But nothing seemed to help.

I came across some articles referencing multiple DataBind calls to the GridView.  I decided to look into my data binding some more.  I decided that instead of using the DefaultValue on my SelectParameters I would set them manually using code behind.  I tried setting it in Page_Load first, but noticed that it would not fire.  I then decided to add it to Page_Init and voila, everything now works.  Here is that code behind:

protected void Page_Init(object sender, EventArgs e)
{
    char c = Char.ToUpper(Utilities.GetCharFromString(Request.QueryString["Filter"]));
    // Check if we need to filter results
    if (c != ' ')
    {
        if (Regex.Match(c.ToString(), "([0-9]|[A-Z])").Success)
        {
            // Ok, we are valid, start the filtering
            System.Diagnostics.Debug.WriteLine("Character is a valid object: " + c.ToString());
            ObjectDataSource1.SelectParameters["Char"] = new Parameter("Char", TypeCode.String, c.ToString());
        }
        else
        {
            System.Diagnostics.Debug.WriteLine("Character is not a valid object: " + c.ToString());
            ObjectDataSource1.SelectParameters["Char"] = new Parameter("Char", TypeCode.String, " ");
        }
    }
    else
    {
        System.Diagnostics.Debug.WriteLine("Character is null");
        ObjectDataSource1.SelectParameters["Char"] = new Parameter("Char", TypeCode.String, " ");
    }
}

If you would like further information, feel free to send me an e-mail

Update (Wedmesday March 11, 2007 @ 8:50:07 PM):

Well, now I feel like a complete and utter dope.  The problem lied deeper than I originally thought.  The reason that this worked is because it caused the ObjectDataSource to essentially be "refreshed".  Why did it need it?  Well, that is because of a hack I did within my master page.

I had a couple of stylesheets in my MasterPage that were created using <% Page.ResolveUrl("~/Path/To/StyleSheet.css") %>. Later, I needed to add, from code behind, more stylesheets and javascript to the head section.  That is when I came across the error: The Controls collection cannot be modified because the control contains code blocks (i.e. <% ... %>).  After doing quite a bit of research on this, I found a solution.  From within my MasterPage's codebehind I would add:

Then I had to change my opening tag on the stylesheet references to use the databinding code blocks, <%#  %>, I could then add anything I needed to the header by using Page.Header.Controls.Add(Control);

This, unfortunately, had the side affect a week and a half later of interfering with any data bound controls, of which this article was written about the first.

After having a chance to reflect upon all of these quirks I have found a workaround that allows me to maintain the header's ability to dynamically set the appropriate stylesheet path via Page.ResolveUrl().  The solution was to only call DataBind() on the header.  In the above code block for OnInit you need to change the Page.DataBind() to Page.Header.DataBind().  Now, the code will parse the header, set it to allow more controls to be added and not interfere with databound elements on your page.

Hopefully this article was of help to somebody.

protected override void OnInit(EventArgs e)
{
    Page.DataBind();
    base.OnInit(e);
}

Comments


Leave Comment

  

  

  




Are you human? Prove it!