中继器对来自单个数据库的项目进行分组
Repeater grouping items from single database
我有一个 table headers(示例):Group、DisplayName、EditableData、描述。
在过去的几天里,我一直在尝试学习转发器以根据组对信息进行排序,这样我就可以获得类似于以下布局的内容。这是我试图放在一起的用户控件。
Group1
------------------
Name1 EditableData Some Description
Name2 EditableData Some Description
Group2
------------------
Name3 EditableData Some Description
Name4 EditableData Some Description
我在网上查看了以下其他示例:
Nested repeaters - grouping data 和
Nested Repeaters in ASP.NET
我相信我没有正确理解中继器如何工作足以处理嵌套或数据源。
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
Group: <%#Eval("Group")%><br />
<%--Nested Repeater would go here for all the children info for each "Group"--%>
</ItemTemplate>
</asp:Repeater>
在我的 SQL 中使用 DISTINCT 来检索“组”,让我得到正确的组而没有重复,我想我可以改为在标签中设置组,然后为每个特定标签制作中继器。 .. 当我稍后将 editable 数据更新回数据库时,这似乎很糟糕。
我想我真正想要的至少是一个 link 的演练,它解释了中继器如何与 Eval() 和数据源一起工作。我的意思是,完成项目第一步所需的一切代码将是完美的;P 但我也希望能够更好地理解这些,因为我可能会在不久的将来经常使用它们。
我曾经遇到过同样的问题,我要按组对数据进行排序,并且必须在分组段中显示公共项目。
有of-course多种检索数据的方法,例如,您可以获取不同的组名并将其绑定到转发器,然后在 ItemDataBound
事件上您可以执行并获取其他这样的元素:
<asp:Repeater runat="server" ID="rptrGroups" OnItemDataBound="rptrGroups_ItemDataBound">
<ItemTemplate>
<asp:Label runat="server" ID="lblGroupName" Text='<%# Eval("GroupName") %>' />
<asp:GridView runat="server" ID="gv">
</asp:GridView>
</ItemTemplate>
</asp:Repeater>
protected void rptrGroups_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item)
{
var lblGroupName = (Label)e.Item.FindControl("lblGroupName");
GridView gv = (GridView)e.Item.FindControl("table");
var dataTable = FetchDataWithGroupName(lblGroupName.Text); // Method that fetches data with groupname.
gv.DataSource = dataTable;
gv.DataBind();
}
}
这不是推荐的方法,因为它会进入数据库,运行查询,然后为每个项目获取数据(如果您从数据库中获取此数据)。如果您有数千个组,那么它将进行数千次数据库调用,这是一件坏事。
第二种解决方案是,您设计一个模型并提供一个自定义模型来完成这项工作。让我用一个示例模型来解释它:
public class GroupedModel
{
public string GroupName {get; set;}
public List<NestedData> TableData {get; set;}
}
public class NestedData
{
public string Id {get; set;}
// Your columns here...
}
然后查询并初始化 GroupedModel
class 的列表,然后将其提供给 repeater
。让我用一些虚拟数据来做。
var tableData = new List<NestedData>();
var nestedData1 = new NestedData { Id = "1" };
var nestedData2 = new NestedData { Id = "2" };
tableData.Add(nestedData1);
tableData.Add(nestedData2);
var groupedModel = new GroupedModel
{
GroupName = "Group1",
TableData = tableData
};
var listGroupedModel = new List<GroupedModel>();
listGroupedModel.Add(groupedModel);
rptrGroups.DataSource = listGroupedModel;
然后像这样修改标记:
<asp:Repeater runat="server" ID="rptrGroups">
<ItemTemplate>
<asp:Label runat="server" ID="lblGroupName" Text='<%# Eval("GroupName") %>' />
<asp:GridView runat="server" ID="gv" DataSource='<%# ((GroupedModel)Container.DataItem).TableData %>'>
</asp:GridView>
</ItemTemplate>
</asp:Repeater>
我发现仅使用网格视图或列表视图效果很好。但是,请勿尝试使用分组功能 - 因为它用于将项目放置在页面上,而不是向下。
让这变得非常简单!
好的,我有一个酒店列表,但我想按城市分组。
因此,您构建这样的查询:
Dim strSQL As String =
"SELECT ID, FirstName, LastName, HotelName, City FROM tblHotels ORDER BY City, HotelName"
GridView1.DataSource = Myrst(strSQL)
GridView1.DataBind()
好的,这就填满了我们的网格视图。我们得到这个:
到目前为止,两行代码!
但是,我们想按城市分组。
所以在表单 class 级别,添加简单的 var:
Public Class HotelGroupGrid
Inherits System.Web.UI.Page
Dim LastCity As String <----- this one
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If IsPostBack = False Then
LastCity = ""
Call LoadGrid()
End If
End Sub
好的,现在在数据项绑定事件上,只需添加一个新行。
代码如下所示:
If e.Row.RowType = DataControlRowType.DataRow Then
' if grouping = 1 then create a new row!
Dim gvRow As DataRowView = DirectCast(e.Row.DataItem, DataRowView)
If gvRow("City") <> LastCity Then
LastCity = gvRow("City")
' insert a new row for grouping header
Dim MyRow As New GridViewRow(-1, -1, DataControlRowType.DataRow, DataControlRowState.Normal)
Dim MyCel As New TableCell()
'MyCel.Width = Unit.Percentage(100)
Dim MyTable As Table = e.Row.Parent
MyCel.ColumnSpan = MyTable.Rows(0).Controls.Count
Dim MyLable As New Label
MyLable.Text = "<h2>" & gvRow("City") & "</h2>"
MyCel.Controls.Add(MyLable)
MyRow.Cells.Add(MyCel)
MyTable.Rows.AddAt(MyTable.Rows.Count - 1, MyRow)
End If
End If
现在,上面是一小部分要细细品味的内容 - 但代码仍然不多。
所以,现在当我们在上面 运行 时,我们得到这个:
我们的网格视图标记如下所示:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ID">
<Columns>
<asp:BoundField DataField="City" HeaderText="City" InsertVisible="False" ReadOnly="True" SortExpression="ID" />
<asp:BoundField DataField="ID" HeaderText="ID" InsertVisible="False" ReadOnly="True" SortExpression="ID" />
<asp:BoundField DataField="FirstName" HeaderText="FirstName" SortExpression="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="LastName" SortExpression="LastName" />
<asp:BoundField DataField="HotelName" HeaderText="HotelName" SortExpression="HotelName" />
</Columns>
</asp:GridView>
所以,还不错。
如果您决定使用列表视图?然后代码变得少了很多,但是listview的标记却很麻烦。
我们所做的就是创建一个行作为我们的标题,然后根据新分组的开始显示或隐藏该行。
那么,如果决定使用列表视图呢?然后我们得到这个:
(我假设您使用数据绑定向导 - 您不可能手动输入标记 - 对吧? - 在这里拯救世界贫困)
因此,对于列表视图(我认为列表视图更好,因为该标题行的布局选项对任何类型的标记和您梦寐以求的额外控件都是开放的。
因此,标记(生成 - 然后切掉脂肪)是这样的:
<asp:ListView ID="ListView1" runat="server" DataKeyNames="ID">
<EmptyDataTemplate>
<table runat="server" style="">
<tr><td>No data was returned.</td></tr>
</table>
</EmptyDataTemplate>
<ItemTemplate>
<tr id="GroupHeading" runat="server" style="display:none">
<td colspan="4">
<h2><asp:Label ID="City" runat="server" Text='<%# Eval("City") %>' /></h2>
</td>
</tr>
<tr>
<td><asp:Label ID="IDLabel" runat="server" Text='<%# Eval("ID") %>' /></td>
<td><asp:Label ID="FirstNameLabel" runat="server" Text='<%# Eval("FirstName") %>' /></td>
<td><asp:Label ID="LastNameLabel" runat="server" Text='<%# Eval("LastName") %>' /></td>
<td><asp:Label ID="HotelNameLabel" runat="server" Text='<%# Eval("HotelName") %>' /></td>
</tr>
</ItemTemplate>
<LayoutTemplate>
<table id="itemPlaceholderContainer" runat="server" border="0" style="">
<tr runat="server">
<th runat="server">ID</th>
<th runat="server">FirstName</th>
<th runat="server">LastName</th>
<th runat="server">HotelName</th>
</tr>
<tr id="itemPlaceholder" runat="server">
</tr>
</table>
</LayoutTemplate>
</asp:ListView>
毫无疑问,列表视图显示出更多的标记 - 但我们现在有一个完整的标题行。所以我们得到这个:
但是,现在我们的代码将简单地隐藏或显示我们在市场中的“额外”行。
现在很简单:
If e.Item.GetType = GetType(ListViewDataItem) Then
Dim MyRow As HtmlTableRow = e.Item.FindControl("GroupHeading")
Dim lblCity As Label = MyRow.FindControl("City")
If lblCity.Text <> LastCity Then
LastCity = lblCity.Text
' Hide/show group heading
MyRow.Style("display") = "normal"
Else
MyRow.Style("display") = "none"
End If
End If
因此,在大多数情况下,诀窍是简单地布置额外的行项目,然后在项目数据绑定事件上,您只需隐藏或显示该标题部分。
我有一个 table headers(示例):Group、DisplayName、EditableData、描述。 在过去的几天里,我一直在尝试学习转发器以根据组对信息进行排序,这样我就可以获得类似于以下布局的内容。这是我试图放在一起的用户控件。
Group1
------------------
Name1 EditableData Some Description
Name2 EditableData Some Description
Group2
------------------
Name3 EditableData Some Description
Name4 EditableData Some Description
我在网上查看了以下其他示例: Nested repeaters - grouping data 和 Nested Repeaters in ASP.NET
我相信我没有正确理解中继器如何工作足以处理嵌套或数据源。
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
Group: <%#Eval("Group")%><br />
<%--Nested Repeater would go here for all the children info for each "Group"--%>
</ItemTemplate>
</asp:Repeater>
在我的 SQL 中使用 DISTINCT 来检索“组”,让我得到正确的组而没有重复,我想我可以改为在标签中设置组,然后为每个特定标签制作中继器。 .. 当我稍后将 editable 数据更新回数据库时,这似乎很糟糕。
我想我真正想要的至少是一个 link 的演练,它解释了中继器如何与 Eval() 和数据源一起工作。我的意思是,完成项目第一步所需的一切代码将是完美的;P 但我也希望能够更好地理解这些,因为我可能会在不久的将来经常使用它们。
我曾经遇到过同样的问题,我要按组对数据进行排序,并且必须在分组段中显示公共项目。
有of-course多种检索数据的方法,例如,您可以获取不同的组名并将其绑定到转发器,然后在 ItemDataBound
事件上您可以执行并获取其他这样的元素:
<asp:Repeater runat="server" ID="rptrGroups" OnItemDataBound="rptrGroups_ItemDataBound">
<ItemTemplate>
<asp:Label runat="server" ID="lblGroupName" Text='<%# Eval("GroupName") %>' />
<asp:GridView runat="server" ID="gv">
</asp:GridView>
</ItemTemplate>
</asp:Repeater>
protected void rptrGroups_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item)
{
var lblGroupName = (Label)e.Item.FindControl("lblGroupName");
GridView gv = (GridView)e.Item.FindControl("table");
var dataTable = FetchDataWithGroupName(lblGroupName.Text); // Method that fetches data with groupname.
gv.DataSource = dataTable;
gv.DataBind();
}
}
这不是推荐的方法,因为它会进入数据库,运行查询,然后为每个项目获取数据(如果您从数据库中获取此数据)。如果您有数千个组,那么它将进行数千次数据库调用,这是一件坏事。
第二种解决方案是,您设计一个模型并提供一个自定义模型来完成这项工作。让我用一个示例模型来解释它:
public class GroupedModel
{
public string GroupName {get; set;}
public List<NestedData> TableData {get; set;}
}
public class NestedData
{
public string Id {get; set;}
// Your columns here...
}
然后查询并初始化 GroupedModel
class 的列表,然后将其提供给 repeater
。让我用一些虚拟数据来做。
var tableData = new List<NestedData>();
var nestedData1 = new NestedData { Id = "1" };
var nestedData2 = new NestedData { Id = "2" };
tableData.Add(nestedData1);
tableData.Add(nestedData2);
var groupedModel = new GroupedModel
{
GroupName = "Group1",
TableData = tableData
};
var listGroupedModel = new List<GroupedModel>();
listGroupedModel.Add(groupedModel);
rptrGroups.DataSource = listGroupedModel;
然后像这样修改标记:
<asp:Repeater runat="server" ID="rptrGroups">
<ItemTemplate>
<asp:Label runat="server" ID="lblGroupName" Text='<%# Eval("GroupName") %>' />
<asp:GridView runat="server" ID="gv" DataSource='<%# ((GroupedModel)Container.DataItem).TableData %>'>
</asp:GridView>
</ItemTemplate>
</asp:Repeater>
我发现仅使用网格视图或列表视图效果很好。但是,请勿尝试使用分组功能 - 因为它用于将项目放置在页面上,而不是向下。
让这变得非常简单!
好的,我有一个酒店列表,但我想按城市分组。
因此,您构建这样的查询:
Dim strSQL As String =
"SELECT ID, FirstName, LastName, HotelName, City FROM tblHotels ORDER BY City, HotelName"
GridView1.DataSource = Myrst(strSQL)
GridView1.DataBind()
好的,这就填满了我们的网格视图。我们得到这个:
到目前为止,两行代码!
但是,我们想按城市分组。
所以在表单 class 级别,添加简单的 var:
Public Class HotelGroupGrid
Inherits System.Web.UI.Page
Dim LastCity As String <----- this one
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If IsPostBack = False Then
LastCity = ""
Call LoadGrid()
End If
End Sub
好的,现在在数据项绑定事件上,只需添加一个新行。
代码如下所示:
If e.Row.RowType = DataControlRowType.DataRow Then
' if grouping = 1 then create a new row!
Dim gvRow As DataRowView = DirectCast(e.Row.DataItem, DataRowView)
If gvRow("City") <> LastCity Then
LastCity = gvRow("City")
' insert a new row for grouping header
Dim MyRow As New GridViewRow(-1, -1, DataControlRowType.DataRow, DataControlRowState.Normal)
Dim MyCel As New TableCell()
'MyCel.Width = Unit.Percentage(100)
Dim MyTable As Table = e.Row.Parent
MyCel.ColumnSpan = MyTable.Rows(0).Controls.Count
Dim MyLable As New Label
MyLable.Text = "<h2>" & gvRow("City") & "</h2>"
MyCel.Controls.Add(MyLable)
MyRow.Cells.Add(MyCel)
MyTable.Rows.AddAt(MyTable.Rows.Count - 1, MyRow)
End If
End If
现在,上面是一小部分要细细品味的内容 - 但代码仍然不多。
所以,现在当我们在上面 运行 时,我们得到这个:
我们的网格视图标记如下所示:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ID">
<Columns>
<asp:BoundField DataField="City" HeaderText="City" InsertVisible="False" ReadOnly="True" SortExpression="ID" />
<asp:BoundField DataField="ID" HeaderText="ID" InsertVisible="False" ReadOnly="True" SortExpression="ID" />
<asp:BoundField DataField="FirstName" HeaderText="FirstName" SortExpression="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="LastName" SortExpression="LastName" />
<asp:BoundField DataField="HotelName" HeaderText="HotelName" SortExpression="HotelName" />
</Columns>
</asp:GridView>
所以,还不错。
如果您决定使用列表视图?然后代码变得少了很多,但是listview的标记却很麻烦。
我们所做的就是创建一个行作为我们的标题,然后根据新分组的开始显示或隐藏该行。
那么,如果决定使用列表视图呢?然后我们得到这个:
(我假设您使用数据绑定向导 - 您不可能手动输入标记 - 对吧? - 在这里拯救世界贫困)
因此,对于列表视图(我认为列表视图更好,因为该标题行的布局选项对任何类型的标记和您梦寐以求的额外控件都是开放的。
因此,标记(生成 - 然后切掉脂肪)是这样的:
<asp:ListView ID="ListView1" runat="server" DataKeyNames="ID">
<EmptyDataTemplate>
<table runat="server" style="">
<tr><td>No data was returned.</td></tr>
</table>
</EmptyDataTemplate>
<ItemTemplate>
<tr id="GroupHeading" runat="server" style="display:none">
<td colspan="4">
<h2><asp:Label ID="City" runat="server" Text='<%# Eval("City") %>' /></h2>
</td>
</tr>
<tr>
<td><asp:Label ID="IDLabel" runat="server" Text='<%# Eval("ID") %>' /></td>
<td><asp:Label ID="FirstNameLabel" runat="server" Text='<%# Eval("FirstName") %>' /></td>
<td><asp:Label ID="LastNameLabel" runat="server" Text='<%# Eval("LastName") %>' /></td>
<td><asp:Label ID="HotelNameLabel" runat="server" Text='<%# Eval("HotelName") %>' /></td>
</tr>
</ItemTemplate>
<LayoutTemplate>
<table id="itemPlaceholderContainer" runat="server" border="0" style="">
<tr runat="server">
<th runat="server">ID</th>
<th runat="server">FirstName</th>
<th runat="server">LastName</th>
<th runat="server">HotelName</th>
</tr>
<tr id="itemPlaceholder" runat="server">
</tr>
</table>
</LayoutTemplate>
</asp:ListView>
毫无疑问,列表视图显示出更多的标记 - 但我们现在有一个完整的标题行。所以我们得到这个:
但是,现在我们的代码将简单地隐藏或显示我们在市场中的“额外”行。
现在很简单:
If e.Item.GetType = GetType(ListViewDataItem) Then
Dim MyRow As HtmlTableRow = e.Item.FindControl("GroupHeading")
Dim lblCity As Label = MyRow.FindControl("City")
If lblCity.Text <> LastCity Then
LastCity = lblCity.Text
' Hide/show group heading
MyRow.Style("display") = "normal"
Else
MyRow.Style("display") = "none"
End If
End If
因此,在大多数情况下,诀窍是简单地布置额外的行项目,然后在项目数据绑定事件上,您只需隐藏或显示该标题部分。