Entity Framework技术系列之6:数据绑定
前言
ASP.NET针对各种数据访问技术,均提供了数据源控件,以实现在页面对数据进行直接绑定。下图是ASP.NET中数据源控件架构图:
图1 ASP.NET数据源控件架构图
由上图可见,针对Entity Framework的实体数据模型,ASP.NET提供了EntityDataSource数据源控件,在ASP.NET页面绑定实体数据。通过与ASP.NET的其他数据绑定控件进行配合,你甚至可以不用在后台写任何代码,就可以轻松实现实体数据的CRUD功能。同时,该数据源控件还可以加载关联对象数据,通过它可以实现丰富的页面功能。
本文接下来就在上一篇文章中使用的实体数据模型的基础上,实现一个包含查询、添加、修改和删除的用户信息列表页面和一个显示、添加和修改用户详细信息的表单页面,展示EntityDataSource控件如何应用于常见的ASP.NET WebForm应用场景中。
列表
在列表页面中,我将实现用户及其详细信息实体数据的列表显式,以及根据输入条件进行模糊查询筛选,并支持在列表中添加、修改和删除用户实体数据。
一、列表显式
首先在页面中放入一个EntityDataSource控件(可以通过工具箱拖拽到页面,也可以在页面的代码视图中手动编码,后同),并设置其相关属性:
1 <asp:EntityDataSource ID="edsUsers" runat="server" ConnectionString="name=Membership" DefaultContainerName="Membership" EntitySetName="Users" Include="UserDetail" OrderBy="it.Account"> 2 </asp:EntityDataSource>
由于需要在列表中展示用户详细信息,所以这里需要显式指定Include属性加载UserDetail实体数据。注意,EntityDataSource不支持延迟加载,需要显式指定需要的关联实体对象,否则将不会加载,这与通过实体上下文环境获取实体数据时的情况不太一样。
ASP.NET页面列表数据绑定控件有很多选择,如DataGrid、GridView、Repeater、ListView等。本文选择ListView作为列表数据绑定控件。在页面中放入一个ListView控件,并设置其相关属性和模板内容:
1 <asp:ListView ID="lvUsers" runat="server" DataSourceID="edsUsers" > 2 <LayoutTemplate> 3 <table border="1" width="800"> 4 <thead> 5 <tr> 6 <th>帐号</th> 7 <th width="200">姓名</th> 8 <th width="60">性别</th> 9 <th width="120">生日</th> 10 <th width="150">操作</th> 11 </tr> 12 </thead> 13 <tbody> 14 <tr id="itemPlaceholder" runat="server"></tr> 15 </tbody> 16 </table> 17 <uc:Pager ID="Pager1" runat="server" /> 18 </LayoutTemplate> 19 <ItemTemplate> 20 <tr> 21 <td><a href='Form.aspx?ID=<%# Eval("ID") %>' target="_blank"><%# Eval("Account") %></a></td> 22 <td><%# Eval("UserDetail.Name") %></td> 23 <td><%# Eval("UserDetail.Sex") %></td> 24 <td><%# Eval("UserDetail.Birthday", "{0:yyyy-MM-dd}")%></td> 25 <td></td> 26 </tr> 27 </ItemTemplate> 28 <EmptyDataTemplate> 29 没有符合条件的数据 30 </EmptyDataTemplate> 31 </asp:ListView>
在布局上这里用了一点小技巧,帐号列没有设置宽度,这样可以根据父级标签元素的宽度来自适应。
Pager是对ASP.NET的DataPager控件进行简单封装的用户控件。ListView控件搭配DataPager控件可以实现数据库层的分页查询。Pager用户控件主要代码如下:
1 <div id="pager"> 2 <asp:DataPager ID="DataPager" runat="server" PageSize="20"> 3 <Fields> 4 <asp:NextPreviousPagerField ButtonType="Image" ButtonCssClass="ImageButton-Pager" ShowFirstPageButton="true" ShowNextPageButton="false" 5 ShowPreviousPageButton="true" ShowLastPageButton="false" FirstPageImageUrl="~/Images/icon_first_16x16.png" 6 PreviousPageImageUrl="~/Images/icon_previous_16x16.png" RenderNonBreakingSpacesBetweenControls="False" /> 7 <asp:NumericPagerField ButtonCount="6" CurrentPageLabelCssClass="LinkButton-Pager-Current" NumericButtonCssClass="LinkButton-Pager" /> 8 <asp:NextPreviousPagerField ButtonType="Image" ButtonCssClass="ImageButton-Pager" ShowFirstPageButton="false" ShowNextPageButton="true" 9 ShowPreviousPageButton="false" ShowLastPageButton="true" LastPageImageUrl="~/Images/icon_last_16x16.png" 10 NextPageImageUrl="~/Images/icon_next_16x16.png" RenderNonBreakingSpacesBetweenControls="False" /> 11 <asp:TemplatePagerField> 12 <PagerTemplate> 13 <%# Container.PageSize %> 条/页,共 <%# Math.Ceiling((double)Container.TotalRowCount / Container.PageSize)%> 页,合计共 <%# Container.TotalRowCount %> 条记录 14 </PagerTemplate> 15 </asp:TemplatePagerField> 16 </Fields> 17 </asp:DataPager> 18 </div>
浏览页面,已经可以看到列表里显示了用户实体数据(上一篇文章的源码中的单元测试可以为你完成测试数据的准备工作),如下图所示:
图2 简单列表
二、条件查询
图2中的列表功能非常简单,不具有互动性,接下来为它增加按条件查询筛选功能。首先,在页面中加入查询表单控件:
1 帐号: 2 <asp:TextBox ID="tbAccount" runat="server" /> 3 <asp:Button ID="btnSearch" runat="server" Text="搜索" />
然后,需要在EntityDataSource控件中加入WhereParameters查询参数。WhereParameters支持多种参数,包括ControlParameter、QueryStringParameter、FormParameter、SessionParameter、CookieParameter等等,用于不同的参数值来源。这里使用ControlParameter,从搜索表单控件获取参数值,代码如下所示:
1 <asp:EntityDataSource ID="edsUsers" runat="server" ConnectionString="name=Membership" DefaultContainerName="Membership" EntitySetName="Users" Include="UserDetail" AutoGenerateWhereClause="true"> 2 <WhereParameters> 3 <asp:ControlParameter Name="Account" DbType="String" ControlID="tbAccount" PropertyName="Text" ConvertEmptyStringToNull="true" /> 4 </WhereParameters> 5 </asp:EntityDataSource>
注意,除了为EntityDataSource控件加入WhereParameters,还需要设置AutoGenerateWhereClause=”true”。
运行页面,发现此时列表已支持查询功能。但是细心的读者会发现有一个问题,它不支持模糊查询。这是因为EntityDataSource控件自动生成的Where语句都是精确匹配的,要支持模糊查询,需要自定义Where属性,并设置AutoGenerateWhereClause=”false”,或不设置(该属性默认值为“false”):
1 <asp:EntityDataSource ID="edsUsers" runat="server" ConnectionString="name=Membership" DefaultContainerName="Membership" EntitySetName="Users" Include="UserDetail" Where="@Account is null or it.Account like '%'+@Account+'%'">
运行页面,模糊查询功能测试通过,如下图所示:
图3 模糊查询
三、添加实体数据
EntityDataSource控件支持实体添加功能,只需要做很少的工作。首先,在ListView控件中加入InsertItemTemplate模板内容项:
1 <asp:ListView ID="lvUsers" runat="server" DataSourceID="edsUsers" InsertItemPosition="LastItem"> 2 … 3 <InsertItemTemplate> 4 <tr> 5 <td><asp:TextBox ID="tbAccount" runat="server" Text='<%# Bind("Account") %>' /></td> 6 <td></td> 7 <td></td> 8 <td></td> 9 <td><asp:Button ID="btnAdd" runat="server" Text="添加" CommandName="Insert" ValidationGroup="Insert" /></td> 10 </tr> 11 </InsertItemTemplate> 12 … 13 </asp:ListView>
这里有两个地方非常关键,一是将实体属性与控件值进行绑定,二是设置提交按钮的事件名称为“Insert”。另外,别忘了设置ListView的InsertItemPosition属性,否则添加表单将不会显示。
然后,需要设置EntityDataSource控件的EnableInsert=”true”,使其允许通过它添加实体数据。另外,可以通过EntityDataSource控件的Inserted事件重新绑定数据,以实现页面数据刷新。前台代码如下:
1 <asp:EntityDataSource ID="edsUsers" runat="server" ConnectionString="name=Membership" DefaultContainerName="Membership" EntitySetName="Users" Include="UserDetail" Where="@Account is null or it.Account like '%'+@Account+'%'" EnableInsert="true" OnInserted="edsUsers_Changed">
后台事件代码如下:
1 protected void edsUsers_Changed(object sender, EntityDataSourceChangedEventArgs e) 2 { 3 this.edsUsers.DataBind(); 4 }
可以通过InsertParameters为添加操作提供参数,这种需求一般发生在以非表单的方式提供实体属性数据的情况下,比如本例中由于密码不能为空,而且也不打算在页面提供直接录入表单,就可以通过参数为其设定默认值,如下所示:
1 <InsertParameters> 2 <asp:Parameter Name="Password" DbType="String" DefaultValue="11111111" /> 3 </InsertParameters>
运行页面,实体添加功能测试通过,如下图所示:
图4 添加实体数据
四、更新实体数据
更新实体数据功能的实现与添加实体数据类似。首先,在ListView控件中加入EditItemTemplate模板项:
1 <EditItemTemplate> 2 <tr> 3 <td><asp:TextBox ID="tbAccount" runat="server" Text='<%# Bind("Account") %>' /></td> 4 <td><%# Eval("UserDetail.Name") %></td> 5 <td><%# Eval("UserDetail.Sex") %></td> 6 <td><%# Eval("UserDetail.Birthday", "{0:yyyy-MM-dd}")%></td> 7 <td> 8 <asp:Button ID="tbnEdit" runat="server" Text="保存" CommandName="Update" ValidationGroup="Update" /> 9 <asp:Button ID="tbnCancel" runat="server" Text="取消" CommandName="Cancel" CausesValidation="false" /> 10 </td> 11 </tr> 12 </EditItemTemplate>
然后,还需要在ItemTemplate模板项中加入更新按钮控件:
1 <ItemTemplate> 2 <tr> 3 <td><a href='Form.aspx?ID=<%# Eval("ID") %>' target="_blank"><%# Eval("Account") %></a></td> 4 <td><%# Eval("UserDetail.Name") %></td> 5 <td><%# Eval("UserDetail.Sex") %></td> 6 <td><%# Eval("UserDetail.Birthday", "{0:yyyy-MM-dd}")%></td> 7 <td> 8 <asp:Button ID="btnEdit" runat="server" Text="修改" CommandName="Edit" CommandArgument='<%#Eval("ID")%>'/> 9 </td> 10 </tr> 11 </ItemTemplate>
然后设置EntityDataSource控件允许更新,以及更新完成后重新绑定数据:
1 <asp:EntityDataSource ID="edsUsers" runat="server" ConnectionString="name=Membership" DefaultContainerName="Membership" EntitySetName="Users" Include="UserDetail" Where="@Account is null or it.Account like '%'+@Account+'%'" EnableInsert="true" EnableUpdate="true" OnInserted="edsUsers_Changed" OnUpdated="edsUsers_Changed">
运行页面,测试更新实体数据功能通过,如下图所示:
图5 更新实体数据
五、删除实体数据
EntityDataSource删除实体数据非常简单。首先在ListView控件的ItemTemplate模板项中加入删除按钮:
1 <asp:Button ID="ibtnDelete" runat="server" Text="删除" CommandName="Delete" CommandArgument='<%#Eval("ID")%>' OnClientClick="return confirm('删除的数据不可恢复,确定要执行删除操作吗?');" />
这里需要设置按钮的命令名称属性CommandName=”Delete”,并且设置命令参数属性CommandArgument=’<%#Eval(“ID”)%>’。
然后设置EntityDataSource允许删除,并且在删除后重新绑定数据:
1 <asp:EntityDataSource ID="edsUsers" runat="server" ConnectionString="name=Membership" DefaultContainerName="Membership" EntitySetName="Users" Include="UserDetail" EnableInsert="true" EnableUpdate="true" EnableDelete="true" OrderBy="it.Account" Where="@Account is null or it.Account like '%'+@Account+'%'" OnInserting="edsUsers_Inserting" OnInserted="edsUsers_Changed" OnUpdated="edsUsers_Changed" OnDeleted="edsUsers_Changed">
运行页面,测试更新实体数据功能通过,如下图所示:
图6 删除实体数据
表单
除了列表外,通过表单浏览、添加和更新数据也是常见的数据展示方式。下面就结合FormView控件,讲解如何使用EntityDataSource进行表单数据绑定。
一、数据显示
首先,在表单页面中加入EntityDataSource控件:
1 <asp:EntityDataSource ID="edsUserDetail" runat="server" ConnectionString="name=Membership" DefaultContainerName="Membership" EntitySetName="UserDetails" Where="it.ID = @ID"> 2 <WhereParameters> 3 <asp:QueryStringParameter Name="ID" DbType="Guid" QueryStringField="ID" ConvertEmptyStringToNull="true" /> 4 </WhereParameters> 5 </asp:EntityDataSource>
然后在表单页面中加入FormView控件:
1 <asp:FormView ID="fvUserDetail" runat="server" DataSourceID="edsUserDetail" DefaultMode="ReadOnly"> 2 <ItemTemplate> 3 <table border="1"> 4 <tbody> 5 <tr> 6 <th>姓名</th> 7 <td><%# Eval("Name") %></td> 8 </tr> 9 <tr> 10 <th>性别</th> 11 <td><%# Eval("Sex") %></td> 12 </tr> 13 <tr> 14 <th>生日</th> 15 <td><%# Eval("Birthday", "{0:yyyy-MM-dd}")%></td> 16 </tr> 17 </tbody> 18 </table> 19 <asp:Button ID="btnEdit" runat="server" Text="修改" CommandName="Edit" /> 20 </ItemTemplate> 21 <EmptyDataTemplate> 22 没有满足条件的数据 23 <asp:Button ID="btnAdd" runat="server" Text="添加" CommandName="Add" /> 24 </EmptyDataTemplate> 25 </asp:FormView>
最后,需要在之前的列表页面中,为表单页面添加链接:
1 <asp:ListView ID="lvUsers" runat="server" DataSourceID="edsUsers" InsertItemPosition="LastItem"> 2 … 3 <ItemTemplate> 4 <tr> 5 <td><a href='Form.aspx?ID=<%# Eval("ID") %>' target="_blank"><%# Eval("Account") %></a></td> 6 … 7 </ItemTemplate> 8 … 9 </asp:ListView>
浏览列表页面,点击包含详细信息的帐号链接,就可以进入表单页面了,如下图所示:
图7 浏览视图
二、添加实体数据
设置ListView的DataKeyNames=”ID”和OnItemCommand=”fvUserDetail_ItemCommand”:
1 <asp:FormView ID="fvUserDetail" runat="server" DataSourceID="edsUserDetail" DataKeyNames="ID" DefaultMode="ReadOnly" OnItemCommand="fvUserDetail_ItemCommand">
在EmptyDataTemplate模板项中加入添加按钮:
1 <EmptyDataTemplate> 2 没有满足条件的数据 3 <asp:Button ID="btnAdd" runat="server" Text="添加" CommandName="Add" /> 4 </EmptyDataTemplate>
在页面后台的fvUserDetail_ItemCommand事件方法中,切换FormView控件到添加视图:
1 protected void fvUserDetail_ItemCommand(object sender, FormViewCommandEventArgs e) 2 { 3 if (e.CommandName == "Add") 4 this.fvUserDetail.ChangeMode(FormViewMode.Insert); 5 }
在FormView控件中加入InsertItemTemplate模板项内容:
1 <InsertItemTemplate> 2 <table border="1"> 3 <tbody> 4 <tr> 5 <th>姓名</th> 6 <td><asp:TextBox ID="tbName" runat="server" Text='<%# Bind("Name") %>' /></td> 7 </tr> 8 <tr> 9 <th>性别</th> 10 <td><asp:TextBox ID="tbSex" runat="server" Text='<%# Bind("Sex") %>' /></td> 11 </tr> 12 <tr> 13 <th>生日</th> 14 <td><asp:TextBox ID="tbBirthday" runat="server" Text='<%# Bind("Birthday") %>' /></td> 15 </tr> 16 </tbody> 17 </table> 18 <asp:Button ID="btnConfirm" runat="server" Text="确定" CommandName="Insert" /> 19 <asp:Button ID="btnCancel" runat="server" Text="取消" CommandName="Cancel" /> 20 </InsertItemTemplate>
设置EntityDataSource控件允许插入实体数据,并在插入后重新绑定,以及设置插入参数:
1 <asp:EntityDataSource ID="edsUserDetail" runat="server" ConnectionString="name=Membership" DefaultContainerName="Membership" EntitySetName="UserDetails" Where="it.ID = @ID" EnableInsert="true" OnInserted="edsUserDetail_Changed"> 2 <WhereParameters> 3 <asp:QueryStringParameter Name="ID" DbType="Guid" QueryStringField="ID" ConvertEmptyStringToNull="true" /> 4 </WhereParameters> 5 <InsertParameters> 6 <asp:QueryStringParameter Name="ID" DbType="Guid" QueryStringField="ID" ConvertEmptyStringToNull="true" /> 7 </InsertParameters> 8 </asp:EntityDataSource>
最后,在页面后台的edsUserDetail_Changed事件中重新绑定数据:
1 protected void edsUserDetail_Changed(object sender, EntityDataSourceChangedEventArgs e) 2 { 3 this.edsUserDetail.DataBind(); 4 }
运行列表页面,点击没有详细信息的帐号链接,就能进入表单页面的空数据视图了,如下图所示:
图8 空数据视图
点击“添加”按钮,可以将页面切换到添加视图,如下图所示:
图9 添加视图
三、更新实体数据
首先,在FormView控件的ItemTemplate模板项中添加修改按钮:
1 <ItemTemplate> 2 … 3 <asp:Button ID="btnEdit" runat="server" Text="修改" CommandName="Edit" /> 4 </ItemTemplate>
为FormView控件加入EditItemTemplate模板项:
1 <EditItemTemplate> 2 <table border="1"> 3 <tbody> 4 <tr> 5 <th>姓名</th> 6 <td><asp:TextBox ID="tbName" runat="server" Text='<%# Bind("Name") %>' /></td> 7 </tr> 8 <tr> 9 <th>性别</th> 10 <td><asp:TextBox ID="tbSex" runat="server" Text='<%# Bind("Sex") %>' /></td> 11 </tr> 12 <tr> 13 <th>生日</th> 14 <td><asp:TextBox ID="tbBirthday" runat="server" Text='<%# Bind("Birthday") %>' /></td> 15 </tr> 16 </tbody> 17 </table> 18 <asp:Button ID="btnConfirm" runat="server" Text="确定" CommandName="Update" /> 19 <asp:Button ID="btnCancel" runat="server" Text="取消" CommandName="Cancel" /> 20 </EditItemTemplate>
然后设置EntityDataSource控件允许更新,并在更新操作后重新绑定数据:
1 <asp:EntityDataSource ID="edsUserDetail" runat="server" ConnectionString="name=Membership" DefaultContainerName="Membership" EntitySetName="UserDetails" EnableInsert="true" EnableUpdate="true" Where="it.ID = @ID" OnInserted="edsUserDetail_Changed" OnUpdated="edsUserDetail_Changed">
浏览列表页面,点击包含详细信息的帐号链接,进入表单页面的数据浏览视图,如下图所示:
图10 带修改按钮的浏览视图
点击“修改”按钮,就可以进入用户详细信息更新视图了,如下图所示:
图11 更新视图
总结
本文在简单分析了ASP.NET数据源控件的技术架构后,通过实例依次实现了EntityDataSource结合ListView控件进行数据增删改查,以及结合FormView控件进行数据浏览、添加和更新的功能,这些功能基本上覆盖了50%的页面数据应用场景。
EntityDataSource控件非常强大,但要用好它却不容易,需要不断的实践方能体会它的奇妙之处。另外,你可以在参数控件、事件方法等方面扩展它,使其更强大,更贴近你的项目需求。
下一篇文章将带领到家领略LINQ to Entities查询技术与Entity Framework技术双剑合璧的强大威力。