65.9K
CodeProject 正在变化。 阅读更多。
Home

让 CheckBoxList 支持多对多关系

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (1投票)

2007 年 6 月 29 日

CPOL

4分钟阅读

viewsIcon

42236

以声明式方式实现多对多选择功能的示例。

引言

再一次,我对 ASP.NET (2.0) 中许多控件缺乏基本 GUI 功能感到惊讶,而且寄予厚望的新一代 Web 控件一次又一次地让我们失望!这次我的问题是允许用户为某个项目,从一个复选框列表中(最好使用 CheckBoxList 控件)选择一个或多个相关项目。这种关系存储在数据库中作为多对多关系,其中连接数据库表包含相关项目的键。

我认为 CheckBoxList 控件的一个合理要求是,在从数据库读取时,能够恢复用户之前保存的选择。然而,这并不像应该的那么简单!我在网上看到了许多实现此功能的示例,但没有一个使用新的 SqlDataSource 控件的声明式方法,我这次尝试遵循了这一点……为什么你总是需要扩展基本控件才能获得基本功能!?!

我很抱歉我的消极态度……但通过一些富有创意的(声明式)编程和一些代码修复(非声明式),我设法得到了我想要的(而不必创建自己的派生类)。所以,本文包含解决此问题的代码片段!而且,如果你和我写这篇文章之前的状态一样,也许这篇文章会让你重新找回方向!?

背景

解决此问题的最理想方法是创建我自己的控件,扩展 CheckBoxList 控件的功能。我的方法是创建一个新的属性“DataSelectedField”,用于在将控件数据绑定到 SqlDataSource 控件时使用,就像 DataTextFieldDataValueField 一样。但是,我不选择这种方法是因为原则上不想自己完成所有工作,而且 CheckBoxList 应该已经具备了这些功能!此外,我决心给声明式编程风格一个机会!(不过,如果你已经实现了这一点,请告诉我!;-)

代码

我的数据库表是 A 和 B,有一个连接表 A_B,存储它们之间的关联,其中包含外键列 A_B.AID 和 A_B.BID。重点是 A,它需要与零个或多个 B 相关联。以下 Select 语句检索 B 中的所有行,以及它们与特定 A 的潜在关联(读取 LEFT JOIN

SELECT B.BID, B.Name, 
    CASE
        WHEN A_B.AID IS NULL THEN CAST(B.BID AS varchar(36)) 
    ELSE (CAST(B.BID AS varchar(36)) + '_' + CAST(A_B.AID AS varchar(36)))
    END AS DataValueField
FROM B
    LEFT OUTER JOIN A_B ON (B.BID = A_B.BID AND A_B.AID = @AID)

除了 BID 和 Name,查询还返回用于知道是否应预选项目的“DataValueField”的值,即,预选项目也应该将“<下划线>A_B.AID”连接到它。@AID 是我们关注的当前 A。(所有 ID 都是uniqueidentifier 类型,并在客户端上作为文本处理。)

现在您知道了数据模型。我从 CheckBoxList 开始,然后向下……因此,CheckBoxList 的代码如下所示:

<asp:CheckBoxList ID="checkBoxList" runat="server"
    DataSourceID="sqlDataSource"
    DataTextField="Name"
    DataValueField="DataValueField"
    ...
</asp:CheckBoxList>

引用的 SqlDataSource 代码如下所示:

<asp:SqlDataSource ID="sqlDataSource" runat="server"
    ConnectionString="<%$ ConnectionStrings:ConnectionString %>"
    SelectCommand="<the query as above>"
    InsertCommand="INSERT INTO A_B SELECT @AID, BID FROM B WHERE BID IN ({0})"
    DeleteCommand="DELETE FROM A_B WHERE AID = @AID">
    <SelectParameters>
        <asp:SessionParameter Name="AID" SessionField="ID" />
    </SelectParameters>
    <InsertParameters>
        <asp:SessionParameter Name="AID" SessionField="ID" />
    </InsertParameters>
    <DeleteParameters>
        <asp:SessionParameter Name="AID" SessionField="ID" />
    </DeleteParameters>
</asp:SqlDataSource>

请注意 InsertCommand 的格式占位符(即 {0}),稍后将使用它。但在我们深入将信息保存到数据库之前,我们必须准备 CheckBoxList 以显示预选项目,如下所示:

foreach (ListItem listItem in checkBoxList.Items)
{
    if (listItem.Value != string.Empty)
    {
        string [] split = listItem.Value.Split(new Char[] {'_'});
        listItem.Value = split[0];
        listItem.Selected = (split.Length > 1) ? true : false;
    }
}

这是将 DataValueField 转换为 ValueSelectedChecked,当它从 ListItem 转换为 CheckBox 控件时)字段的地方。此代码应在控件数据绑定后执行,这意味着在 DataBound 事件触发后。

好了,现在我们已经渲染了列表,我们回到保存部分。连接表的核心问题在于,当用户保存时要进行的“更新”实际上是删除未选中的项目并插入新选中的项目。我们需要自己处理这个问题,同时使用插入和删除命令,触发相应的操作。因此,我们不使用 SqlDataSource 的预定义命令,而是从代码中手动触发更新(但我们仍然“声明”了查询)。

这是用于控制更新并调整插入命令的代码:

// Run the DELETE-statement first.
sqlDataSource.Delete();

// Construct the sql INSERT-statement for selected items:
StringBuilder sb = new StringBuilder();
foreach (ListItem listItem in checkBoxList.Items)
{
    if (listItem.Selected)
    {
        if (sb.Length > 0)
            sb.Append(",");
        sb.Append("'");
        sb.Append(listItem.Value);
        sb.Append("'");
    }
}
if (sb.Length > 0)
{
    sqlDataSource.InsertCommand =
        string.Format(
        sqlDataSource.InsertCommand, 
        sb.ToString());
    sqlDataSource.Insert();
}

代码很简单,首先删除所有内容,然后构造 IN 语句列表以对格式占位符执行 string.Format,然后添加列表中所有选中的项目。此代码可以放置在任何地方,例如,在 ButtonClick 事件中。

历史

  • 2007-06-29:初始版本……欢迎提供反馈!
© . All rights reserved.