GridView + datakeynames + multiple PK

Posts   
 
    
Posts: 69
Joined: 24-Jun-2008
# Posted on: 24-Jun-2008 18:30:54   

Hi,

I am trying to delete items from a gridview, but it seems impossible when using multiple foreign keys:


Windows Internet Explorer

There are no primary key fields specified in the bound control and/or the bound control didn't specify any primary key fields. Delete can't continue.

OK

Table

ProjectUser

ProjectID UserID

ProjectID and UserID are both a FK to another table. Together, they form a PK (so I can see the users of a project). I have set the DataKeyNames of the GridView to "ProjectId,UserId" (LLBLGen maps the ProjectID as ProjectId and UserID as UserId).

The same code works for all entities with just 1 PK.

LLBLGen library version: 2.6.0.0

Any ideas what can be wrong?

Best regards,

Walaa avatar
Walaa
Support Team
Posts: 14994
Joined: 21-Aug-2005
# Posted on: 25-Jun-2008 10:50:40   

Can't figure it out without a repro solution?

Also check this thread to know how to get the exact runtime library version: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=7720

Posts: 69
Joined: 24-Jun-2008
# Posted on: 25-Jun-2008 11:19:29   

2.6 Final (Released on June 6th, 2008 )

The tables are simple, just a User table, Project table and a link table called ProjectUser.

Here is the source of the ASP.NET 2.0 gridview:


<%@ Page Language="C#" MasterPageFile="~/MasterSubMenu.master" AutoEventWireup="true" CodeBehind="ManageProjectUsers.aspx.cs" Inherits="CatenaLogic.Website.Admin.ManageProjectUsers" %>

<asp:Content ID="defaultContent" ContentPlaceHolderID="ContentPlaceHolder" Runat="Server">

    <h1>Manage project users</h1>

    <asp:UpdatePanel ID="updatePanel" runat="server">
        <ContentTemplate>
        
            <llblgenpro:LLBLGenProDataSource ID="projectsDataSource" runat="server" 
                DataContainerType="EntityCollection" 
                EntityCollectionTypeName="CatenaLogic.Website.Data.CollectionClasses.ProjectCollection, CatenaLogic.Website.Data" 
                EnablePaging="True">
            </llblgenpro:LLBLGenProDataSource>      
        
            <llblgenpro:LLBLGenProDataSource ID="projectUsersDataSource" runat="server" 
                DataContainerType="EntityCollection" 
                EntityCollectionTypeName="CatenaLogic.Website.Data.CollectionClasses.ProjectUserCollection, CatenaLogic.Website.Data" 
                EnablePaging="True">
                <SelectParameters>
                    <asp:ControlParameter ControlID="projectsDropDownList" Name="ProjectId" PropertyName="SelectedValue" />
                </SelectParameters>
            </llblgenpro:LLBLGenProDataSource>

            <p>
                Project: <asp:DropDownList ID="projectsDropDownList" runat="server" AutoPostBack="true" DataSourceID="projectsDataSource" DataValueField="ProjectId" DataTextField="Name" />
            </p>
            
            <asp:GridView ID="projectUsersGridView" runat="server" AllowPaging="True" 
                DataSourceID="projectUsersDataSource" AutoGenerateColumns="False"
                DataKeyNames="ProjectId,UserId" OnRowDataBound="projectUsersGridView_RowDataBound">
                <Columns>
                    <asp:TemplateField>
                        <HeaderTemplate>User</HeaderTemplate>
                        <ItemTemplate>
                            <asp:Label ID="userLabel" runat="server" />
                            <asp:ImageButton ID="editButton" ImageUrl="~/images/gridview/edit.gif" ToolTip="Edit" runat="server" />
                            <asp:ImageButton ID="deleteButton" ImageUrl="~/images/gridview/delete.gif" ToolTip="Delete" runat="server" />
                        </ItemTemplate>
                    </asp:TemplateField>
                </Columns>
            </asp:GridView>     
            
        </ContentTemplate>
    </asp:UpdatePanel>

</asp:Content>

Here is the (relavant) code-behind:


        protected void projectUsersGridView_RowDataBound(object sender, GridViewRowEventArgs e)
        {
            if (e.Row.Cells.Count > 1)
            {
                // Get user label
                Label userLabel = (e.Row.FindControl("userLabel") != null) ? (Label)e.Row.FindControl("userLabel") : null;

                // Do we have a valid label?
                if (userLabel != null)
                {
                    // Get the data
                    ProjectUserEntity entity = (e.Row.DataItem != null) ? (ProjectUserEntity)e.Row.DataItem : null;

                    // Do we have a valid entity?
                    if (entity != null)
                    {
                        // Set up label
                        userLabel.Text = entity.User.ContactName;
                    }
                }
            }
        }

I also have base class which takes care of the modify/delete buttons:


        protected void GridView_RowDataBound(object sender, GridViewRowEventArgs e)
        {
            // Check row type
            if (e.Row.RowType == DataControlRowType.DataRow)
            {
                // Get buttons
                ImageButton editButton = (e.Row.FindControl(_editButtonName) != null) ? (ImageButton)e.Row.FindControl(_editButtonName) : null;
                ImageButton deleteButton = (e.Row.FindControl(_deleteButtonName) != null) ? (ImageButton)e.Row.FindControl(_deleteButtonName) : null;

                // Get entity base
                EntityBase entity = (e.Row.DataItem != null) ? (EntityBase)e.Row.DataItem : null;

                // Do we have a valid edit button?
                if (editButton != null)
                {
                    // Set command
                    editButton.CommandName = "CustomEdit";

                    // Set command arguments
                    if (entity != null)
                    {
                        editButton.CommandArgument = GetCommandArgument(entity);
                    }
                }

                // Do we have a valid delete button and should we confirm?
                if ((deleteButton != null) && (_confirmBeforeDelete))
                {
                    // Make sure the user should confirm
                    deleteButton.Attributes.Add("onclick", "javascript:return confirm('Are you sure you want to delete this item?')");

                    // Set command
                    deleteButton.CommandName = "Delete";
                }
            }
        }

Walaa avatar
Walaa
Support Team
Posts: 14994
Joined: 21-Aug-2005
# Posted on: 25-Jun-2008 11:28:19   

Maybe that's because inside the <Columns> tag you don't have any columns bound to ProjectId and UserId.

Try to add these columns and make them invisible if needed.

Posts: 69
Joined: 24-Jun-2008
# Posted on: 25-Jun-2008 12:03:14   

This doesn't help either. Also, in my other gridviews (which represent tables that only have 1 PK), all works fine (without adding a bound field to the PK).

Maybe I should use a special formatting for the CommandArgument when deleting an entity with multiple fields as primary key?

Walaa avatar
Walaa
Support Team
Posts: 14994
Joined: 21-Aug-2005
# Posted on: 25-Jun-2008 17:18:39   

Maybe I should use a special formatting for the CommandArgument when deleting an entity with multiple fields as primary key?

I think this might be it, especially if you're using the same technique for editing, and if editing is working.

Posts: 69
Joined: 24-Jun-2008
# Posted on: 25-Jun-2008 17:24:40   

I don't know how to specify the CommandArgument when the Command is "Delete". When using just 1 PK, I don't have to specify any PK or CommandArgument.

I do not handle the code for deleting the object myself (since I don't know how to create an entity from a string).

I have a base class to where I pass the EntityType in a string format, for example ProjectUserEntity. I use this code to modify:

Response.Redirect(string.Format("~/Admin/{0}.aspx?id={1}", _entityType, _objectID));

So, I don't need to know the type in the base class. For deleting, I need to retrieve the object first which is a problem for me. Normally, the gridview handles this for me, but in this case it doesn't.

Any ideas?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39872
Joined: 17-Aug-2003
# Posted on: 25-Jun-2008 17:26:16   

The error is thrown because the Dictionary with the keys passed into ExecuteDelete method on the datasourcecontrol is empty so the datasourcecontrol gets invalid information from the bound control (in this case, the grid). The ExecuteDelete method is called by the grid, so out of your hands.

I indeed think it's about the columns not being bound to real fields. If you add the columns to the grid (just for testing) so you see the pk fields, and you then remove a row, it should work, correct?

Frans Bouma | Lead developer LLBLGen Pro
Posts: 69
Joined: 24-Jun-2008
# Posted on: 25-Jun-2008 17:32:29   

This is the new source for the gridview (I didn't included all the datasources again, they remained the same):


            <asp:GridView ID="projectUsersGridView" runat="server" AllowPaging="True" 
                DataSourceID="projectUsersDataSource" AutoGenerateColumns="False"
                DataKeyNames="ProjectId,UserId" OnRowDataBound="projectUsersGridView_RowDataBound">
                <Columns>
                    <asp:BoundField DataField="ProjectId" />
                    <asp:BoundField DataField="UserId" />
                    <asp:TemplateField>
                        <HeaderTemplate>User</HeaderTemplate>
                        <ItemTemplate>
                            <asp:Label ID="userLabel" runat="server" />
                        </ItemTemplate>
                    </asp:TemplateField>
                    <asp:BoundField DataField="CurrentHourRate" HeaderText="Hour Rate" SortExpression="CurrentHourRate" />
                    <asp:TemplateField>
                        <ItemTemplate>
                            <asp:ImageButton ID="editButton" ImageUrl="~/images/gridview/edit.gif" ToolTip="Edit" runat="server" />
                            <asp:ImageButton ID="deleteButton" ImageUrl="~/images/gridview/delete.gif" ToolTip="Delete" runat="server" />
                        </ItemTemplate>
                    </asp:TemplateField>
                </Columns>
            </asp:GridView>   

Still, no luck...

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 26-Jun-2008 08:03:36   

CatenaLogic wrote:

Still, no luck...

Hi Geert, Did you get the same error or simply the row didn't get deleted?

My test reveal that:

  • There isn't need to add extra BoundFields to get it work
  • I don't know why I can't test it with ImageButton (RowCommand and RowDeleting events aren't fired), I tested successfully with Button control. I don't think you have this problem as you are getting another error disappointed

So, please:

  1. Triple-check you are using the correct DataKeyNames
  2. Only to be sure that this isn't related, test with Button instead of ImageButton.
  3. When debugging your event handlers, the properties are assigned ok to deleteButton? Is this always true? :
if ((deleteButton != null) && (_confirmBeforeDelete))

I also have base class which takes care of the modify/delete buttons:

protected void GridView_RowDataBound(object sender, GridViewRowEventArgs e)
        {
            // Check row type
            if (e.Row.RowType == DataControlRowType.DataRow)
            {

I don't understand this. Shouldn't you add this code to the yourDataGridView_RowDataBound handler?

  1. For the rest, if the problem persist, please make a tiny repro solution as I can reproduce your issue disappointed
David Elizondo | LLBLGen Support Team
Posts: 69
Joined: 24-Jun-2008
# Posted on: 26-Jun-2008 09:36:25   

Found the problem!

I was creating a demo project for you, and that was working fine confused

So, I decided to strip my source until I would get a working example. This is the problem:

I have two datasources:


            <llblgenpro:LLBLGenProDataSource ID="projectsDataSource" runat="server" 
                DataContainerType="EntityCollection" 
                EntityCollectionTypeName="CatenaLogic.Website.Data.CollectionClasses.ProjectCollection, CatenaLogic.Website.Data" 
                EnablePaging="True">
            </llblgenpro:LLBLGenProDataSource>      
        
            <llblgenpro:LLBLGenProDataSource ID="projectUsersDataSource" runat="server" 
                DataContainerType="EntityCollection" 
                EntityCollectionTypeName="CatenaLogic.Website.Data.CollectionClasses.ProjectUserCollection, CatenaLogic.Website.Data" 
                EnablePaging="True">
                <SelectParameters>
                    <asp:ControlParameter ControlID="projectsDropDownList" Name="ProjectId" PropertyName="SelectedValue" />
                </SelectParameters>
            </llblgenpro:LLBLGenProDataSource>

With the first datasource, I fill a combobox which shows all the project so I can easily see all the users of a specific project. Then, the datasource of the projectUsers is filtered using a SelectParameter.

It's the SelectParameter that is causing this problem. As soon as I remove it, all works fine. Now the final question is: why is the SelectParameter in the datasource causing this problem and how can I still filter the users of specific project then?

Walaa avatar
Walaa
Support Team
Posts: 14994
Joined: 21-Aug-2005
# Posted on: 26-Jun-2008 09:58:51   

The only thing that's missing is the Type attribute.

Try the following:

                <SelectParameters>
                    <asp:ControlParameter ControlID="projectsDropDownList" Name="ProjectId" PropertyName="SelectedValue" Type="String"/>
                </SelectParameters>
Posts: 69
Joined: 24-Jun-2008
# Posted on: 26-Jun-2008 12:59:53   

The filtering is working, even without specifying the type. As far as I know, this is not mandatory.

Anyway, I have tested it with adding the type as Int32 (since it's an integer type in the database), but I still get the same error as described in my first message.

Walaa avatar
Walaa
Support Team
Posts: 14994
Joined: 21-Aug-2005
# Posted on: 26-Jun-2008 16:10:58   

Then I have to ask for a small repro solution.

Posts: 69
Joined: 24-Jun-2008
# Posted on: 26-Jun-2008 16:52:17   

My repro solution was working fine... I have compared all the differences, and there was only one:

The repro contained more than 1 project where my real project database only contained one project. As soon as I added another one, it all works fine.

Kinda strange, don't you think?

Posts: 69
Joined: 24-Jun-2008
# Posted on: 26-Jun-2008 17:11:27   

Another update:

When the page is loaded with the default project, I cannot delete an object. As soon as I switch the project and switch it back, it works (so it seems that the combobox doesn't have a valid value the first time).

Posts: 69
Joined: 24-Jun-2008
# Posted on: 26-Jun-2008 17:18:37   

Another update:

When I populate the dropdownlist manually with the code in the Page_Load function, it works immediately (without changing the selection in the dropdown first):


        /// <summary>
        /// Initializes the comboboxes
        /// </summary>
        protected void InitializeComboboxes()
        {
            // Clear all values
            projectsDropDownList.Items.Clear();

            // Now add all projects
            ProjectCollection projectCollection = new ProjectCollection();
            projectCollection.GetMulti(null);
            
            // Add all items
            foreach (ProjectEntity project in projectCollection)
            {
                ListItem item = new ListItem(project.Name, project.ProjectId.ToString());
                projectsDropDownList.Items.Add(item);
            }
        }

The code below also works:


        /// <summary>
        /// Initializes the comboboxes
        /// </summary>
        protected void InitializeComboboxes()
        {
            // Clear all values
            projectsDropDownList.Items.Clear();

            // Now add all projects
            ProjectCollection projectCollection = new ProjectCollection();
            projectCollection.GetMulti(null);

            projectsDropDownList.DataTextField = "Name";
            projectsDropDownList.DataValueField = "ProjectId";
            projectsDropDownList.DataSource = projectCollection;
            projectsDropDownList.DataBind();
        }

Walaa avatar
Walaa
Support Team
Posts: 14994
Joined: 21-Aug-2005
# Posted on: 27-Jun-2008 10:38:46   

It seems that the Databinding of the Grid was ocurring before that of the DropDownList, that's why the SelectParameter fails to grasp the selectedValue.

Please make sure you call the dropdownList's DataBind() method first thing in the PageLoad.

Posts: 69
Joined: 24-Jun-2008
# Posted on: 27-Jun-2008 10:42:22   

Why should I? It's the designer that is taking care of that automatically.

Anyway, I have solved it by manually adding the items which is fine for me. Still I thing it's strange that the data binding is too late and is causing this error.

Maybe this is a good reference for other people that might have the same problem.

Thanks for all your help!

Best regards,