作者 :Tom Dykstra
Contoso University 示例 Web 应用程序演示如何使用 Entity Framework 4.0 和 Visual Studio 2010 创建 ASP.NET Web Forms应用程序。 有关教程系列的信息,请参阅 系列中的第一个教程
EntityDataSource 控件
在上一教程中,你创建了网站、数据库和数据模型。 在本教程中,你将使用 EntityDataSource
ASP.NET 提供的控件,以便轻松使用实体框架数据模型。 你将创建一个 GridView
用于显示和编辑学生数据的控件,一个 DetailsView
用于添加新学生的控件,以及一个 DropDownList
用于选择系 (稍后将用于显示关联课程) 的控件。
请注意,在此应用程序中,不会向更新数据库的页面添加输入验证,并且某些错误处理不会像生产应用程序中那样可靠。 这使得本教程专注于实体框架,避免过长。 有关如何将这些功能添加到应用程序的详细信息,请参阅验证 ASP.NET 网页中的用户输入和 ASP.NET Pages and Applications 中的错误处理。
添加和配置 EntityDataSource 控件
首先,将控件配置为EntityDataSource
从People
实体集中读取Person
实体。
确保已打开 Visual Studio,并且正在使用第 1 部分中创建的项目。 如果自创建数据模型或上次更改数据模型后尚未生成项目,请立即生成项目。 在生成项目之前,不会向设计器提供对数据模型的更改。
使用母版页模板使用 Web 窗体 创建新网页,并将其命名为 Students.aspx。
将 Site.Master 指定为母版页。 为这些教程创建的所有页面都将使用此母版页。
在“源”视图中,将标题添加到h2
Content
名为 Content2
的控件,如以下示例所示:
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Student List</h2>
</asp:Content>
从“工具箱”的“数据”选项卡中,将控件EntityDataSource
拖到页面,将其拖放到标题下方,然后将 ID 更改为 StudentsEntityDataSource
:
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Student List</h2>
<asp:EntityDataSource ID="StudentsEntityDataSource" runat="server">
</asp:EntityDataSource>
</asp:Content>
切换到 “设计” 视图,单击数据源控件的智能标记,然后单击“ 配置数据源 ”以启动 “配置数据源 ”向导。
在 “配置 ObjectContext ”向导步骤中,选择“ SchoolEntities ”作为 “命名连接”的值,并选择“ SchoolEntities ”作为 “DefaultContainerName” 值。 然后单击“下一步”。
注意:如果此时收到以下对话框,则必须在继续之前生成项目。
在“配置数据选择”步骤中,选择“人员”作为 EntitySetName 的值。 在“选择”下,确保选中“选择 A 将检查框。 然后选择选项以启用更新和删除。 完成后,单击“ 完成”。
配置数据库规则以允许删除
你将创建一个页面,允许用户从 Person
表中删除学生,该页面与其他表 (Course
、 StudentGrade
和 OfficeAssignment
) 具有三种关系。 默认情况下,如果其他表中有相关行 Person
,数据库将阻止删除 中的行。 可以先手动删除相关行,也可以将数据库配置为在删除行 Person
时自动删除它们。 对于本教程中的学生记录,你将配置数据库以自动删除相关数据。 由于学生只能在 StudentGrade
表中具有相关行,因此只需配置三个关系中的一个。
如果使用从本教程中的项目下载的 School.mdf 文件,则可以跳过此部分,因为这些配置更改已经完成。 如果通过运行脚本创建了数据库,请通过执行以下过程来配置数据库。
在 服务器资源管理器中,打开您在第 1 部分中创建的数据库关系图。 右键单击 和 StudentGrade
之间的关系Person
, (表) 之间的线条,然后选择“属性”。
在 “属性” 窗口中,展开 “INSERT”和“UPDATE 规范 ”,并将 DeleteRule 属性设置为 Cascade。
保存并关闭关系图。 如果系统询问是否要更新数据库,请单击“ 是”。
若要确保模型使内存中的实体与数据库正在执行的操作保持同步,必须在数据模型中设置相应的规则。 打开 SchoolModel.edmx,右键单击 和 StudentGrade
之间的Person
关联行,然后选择“属性”。
在 “属性” 窗口中,将 “End1 OnDelete” 设置为 Cascade。
保存并关闭 SchoolModel.edmx 文件,然后重新生成项目。
一般情况下,当数据库发生更改时,有多种选择来同步模型:
- 对于某些类型的更改 (如添加或刷新表、视图或存储过程) ,请在设计器中右键单击并选择“ 从数据库更新模型 ”,让设计器自动进行更改。
- 重新生成数据模型。
- 进行手动更新,如下所示。
在这种情况下,可以重新生成模型或刷新受关系更改影响的表,但随后必须再次更改字段名称, (从 FirstName
更改为 FirstMidName
) 。
使用 GridView 控件读取和更新实体
在本部分中,你将使用 控件 GridView
来显示、更新或删除学生。
打开或切换到 Students.aspx 并切换到 “设计” 视图。 从“工具箱”的“数据”选项卡中,将控件GridView
拖到控件右侧EntityDataSource
,将其StudentsGridView
命名为 ,单击智能标记,然后选择“StudentsEntityDataSource”作为数据源。
单击“刷新架构 (如果系统提示确认) ,请单击”是“,然后单击”启用分页“、”启用排序“、”启用编辑“和”启用删除”。
单击“ 编辑列”。
在 “所选字段 ”框中,删除 “PersonID”、“ LastName”和“ HireDate”。 通常不会向用户显示记录密钥,雇佣日期与学生无关,并且将名称的两个部分放在一个字段中,因此只需要其中一个名称字段。)
选择 “FirstMidName” 字段,然后单击“ 将此字段转换为 TemplateField”。
对 EnrollmentDate 执行相同的操作。
单击“ 确定” ,然后切换到 “源” 视图。 其余更改将更容易直接在标记中执行。 控件 GridView
标记现在如以下示例所示。
<asp:GridView ID="StudentsGridView" runat="server" AllowPaging="True"
AllowSorting="True" AutoGenerateColumns="False" DataKeyNames="PersonID"
DataSourceID="StudentsEntityDataSource">
<Columns>
<asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
<asp:TemplateField HeaderText="FirstMidName" SortExpression="FirstMidName">
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("FirstMidName") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Bind("FirstMidName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="EnrollmentDate" SortExpression="EnrollmentDate">
<EditItemTemplate>
<asp:TextBox ID="TextBox2" runat="server" Text='<%# Bind("EnrollmentDate") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label2" runat="server" Text='<%# Bind("EnrollmentDate") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
命令字段后的第一列是当前显示名字的模板字段。 更改此模板字段的标记,如以下示例所示:
<asp:TemplateField HeaderText="Name" SortExpression="LastName">
<EditItemTemplate>
<asp:TextBox ID="LastNameTextBox" runat="server" Text='<%# Bind("LastName") %>'></asp:TextBox>
<asp:TextBox ID="FirstNameTextBox" runat="server" Text='<%# Bind("FirstMidName") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="LastNameLabel" runat="server" Text='<%# Eval("LastName") %>'></asp:Label>,
<asp:Label ID="FirstNameLabel" runat="server" Text='<%# Eval("FirstMidName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
在显示模式下,两个 Label
控件显示名字和姓氏。 在编辑模式下,会提供两个文本框,以便你可以更改名字和姓氏。 与显示模式下的 Label
控件一样,使用 Bind
和 Eval
表达式的方式与直接连接到数据库的数据源控件 ASP.NET 完全相同。 唯一的区别是指定实体属性而不是数据库列。
最后一列是显示注册日期的模板字段。 更改此字段的标记,如以下示例所示:
<asp:TemplateField HeaderText="Enrollment Date" SortExpression="EnrollmentDate">
<EditItemTemplate>
<asp:TextBox ID="EnrollmentDateTextBox" runat="server" Text='<%# Bind("EnrollmentDate", "{0:d}") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="EnrollmentDateLabel" runat="server" Text='<%# Eval("EnrollmentDate", "{0:d}") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
在显示和编辑模式下,格式字符串“{0,d}”会导致日期以“短日期”格式显示。 (你的计算机可能配置为以不同于本教程中显示的屏幕图像显示此格式。)
请注意,在每个模板字段中,设计器默认使用Bind
表达式,但你已将表达式更改为元素中的ItemTemplate
表达式Eval
。 表达式 Bind
使数据在控件属性中 GridView
可用,以防你需要访问代码中的数据。 在此页中,无需在代码中访问此数据,因此可以使用 Eval
更高效的 。 有关详细信息,请参阅 从数据控件中获取数据。
修改 EntityDataSource 控件标记以提高性能
在 控件的标记中 EntityDataSource
,删除 ConnectionString
和 DefaultContainerName
属性,并将其替换为 属性 ContextTypeName="ContosoUniversity.DAL.SchoolEntities"
。 这是每次创建 EntityDataSource
控件时都应做出的更改,除非需要使用与对象上下文类中硬编码的连接不同的连接。 ContextTypeName
使用 属性具有以下优势:
- 性能更好。
EntityDataSource
当 控件使用ConnectionString
和DefaultContainerName
属性初始化数据模型时,它会执行额外的工作来加载每个请求的元数据。 如果指定 属性,则不需要这样做ContextTypeName
。 - 默认情况下,在生成的对象上下文类中打开延迟加载, (如
SchoolEntities
本教程中) Entity Framework 4.0 中所示。 这意味着导航属性将在需要时自动加载相关数据。 本教程稍后会更详细地介绍延迟加载。 - 在这种情况下, (应用于对象上下文类的任何自定义项,
SchoolEntities
类) 将可用于使用该控件的EntityDataSource
控件。 自定义对象上下文类是本教程系列中未介绍的高级主题。 有关详细信息,请参阅 扩展实体框架生成的类型。
标记现在将类似于以下示例, (属性的顺序可能) 不同:
<asp:EntityDataSource ID="StudentsEntityDataSource" runat="server"
ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="False"
EntitySetName="People"
EnableDelete="True" EnableUpdate="True">
</asp:EntityDataSource>
属性 EnableFlattening
是指早期版本的实体框架中所需的功能,因为外键列未作为实体属性公开。 通过当前版本,可以使用 外键关联,这意味着除了多对多关联之外,外键属性将公开。 如果实体具有外键属性且没有 复杂类型,则可以将此属性设置为 False
。 请勿从标记中删除 属性,因为默认值为 True
。 有关详细信息,请参阅 平展对象 (EntityDataSource) 。
运行该页,你将看到学生和员工的列表, (你将在下一个教程) 中仅筛选学生。 名字和姓氏一起显示。
若要对显示进行排序,请单击列名。
单击任意行中的 “编辑 ”。 将显示文本框,你可以在其中更改名字和姓氏。
“ 删除 ”按钮也有效。 对于具有注册日期的行,单击“删除”,该行将消失。 (没有注册日期的行表示讲师,你可能会收到引用完整性错误。在下一教程中,你将筛选此列表以仅包含 students.)
显示导航属性中的数据
现在假设你想知道每个学生注册了多少门课程。 实体框架在实体的StudentGrades
Person
导航属性中提供该信息。 由于数据库设计不允许学生在没有分配成绩的情况下注册课程,因此在本教程中,可以假定在与课程关联的表格行中有 StudentGrade
一行与在课程中注册相同。 (导航 Courses
属性仅适用于 instructors.)
使用 ContextTypeName
控件的 EntityDataSource
属性时,当你访问该属性时,实体框架会自动检索导航属性的信息。 这称为 延迟加载。 但是,这可能效率低下,因为每次需要其他信息时,都会单独调用数据库。 如果需要控件返回 EntityDataSource
的每个实体的导航属性中的数据,则通过在对数据库的单个调用中检索相关数据以及实体本身会更有效。 这称为 预先加载,可以通过设置 Include
控件的 属性来指定导航属性的 EntityDataSource
预先加载。
在 Students.aspx 中,你想要显示每个学生的课程数,因此预先加载是最佳选择。 如果显示所有学生,但只显示其中少数学生的课程数 (这需要编写一些代码以及标记) ,则延迟加载可能是更好的选择。
打开或切换到 Students.aspx,切换到 设计 视图,选择 StudentsEntityDataSource
,然后在 “属性” 窗口中将 Include 属性设置为 StudentGrades。 (如果想要获取多个导航属性,可以指定它们的名称(以逗号分隔),例如 ,StudentGrades、Courses.)
切换到 “源” 视图。 在 StudentsGridView
控件中,在最后一个 asp:TemplateField
元素的后面添加以下新模板字段:
<asp:TemplateField HeaderText="Number of Courses">
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Eval("StudentGrades.Count") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
在表达式中 Eval
,可以引用导航属性 StudentGrades
。 由于此属性包含集合,因此它具有属性 Count
,可用于显示学生注册的课程数。 在后面的教程中,你将了解如何显示包含单个实体而不是集合的导航属性中的数据。 (请注意,不能使用 BoundField
元素显示导航属性中的数据。)
运行该页,现在可以看到每个学生注册的课程数。
使用 DetailsView 控件插入实体
下一步是创建一个 DetailsView
页面,该页面具有可添加新学生的 控件。 关闭浏览器,然后使用 Site.Master 母版页创建新的网页。 将页面命名为 StudentsAdd.aspx,然后切换到 “源” 视图。
添加以下标记以替换名为 Content2
的Content
控件的现有标记:
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Add New Students</h2>
<asp:EntityDataSource ID="StudentsEntityDataSource" runat="server"
ContextTypeName="ContosoUniversity.DAL.SchoolEntities" EnableFlattening="False"
EnableInsert="True" EntitySetName="People">
</asp:EntityDataSource>
<asp:DetailsView ID="StudentsDetailsView" runat="server"
DataSourceID="StudentsEntityDataSource" AutoGenerateRows="False"
DefaultMode="Insert">
<Fields>
<asp:BoundField DataField="FirstMidName" HeaderText="First Name"
SortExpression="FirstMidName" />
<asp:BoundField DataField="LastName" HeaderText="Last Name"
SortExpression="LastName" />
<asp:BoundField DataField="EnrollmentDate" HeaderText="Enrollment Date"
SortExpression="EnrollmentDate" />
<asp:CommandField ShowInsertButton="True" />
</Fields>
</asp:DetailsView>
</asp:Content>
此标记创建的 EntityDataSource
控件类似于在 Students.aspx 中创建的控件,只不过它支持插入。 与 GridView
控件一样,控件的 DetailsView
绑定字段的编码方式与直接连接到数据库的数据控件的编码方式完全相同,只不过它们引用实体属性。 在这种情况下, DetailsView
控件仅用于插入行,因此已将默认模式设置为 Insert
。
运行页面并添加新学生。
插入新学生后不会发生任何操作,但如果现在运行 Students.aspx,你将看到新学生信息。
在Drop-Down列表中显示数据
在以下步骤中,你将使用 EntityDataSource
控件将数据绑定到DropDownList
实体集。 在本教程的这一部分中,你不会对此列表执行太多操作。 不过,在后续部分中,你将使用该列表让用户选择一个部门来显示与该部门关联的课程。
创建名为 Courses.aspx 的新网页。 在 “源” 视图中,将标题添加到 Content
名为 Content2
的控件:
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Courses by Department</h2>
</asp:Content>
在 “设计 ”视图中,像以前一 EntityDataSource
样将控件添加到页面,但这次将其 DepartmentsEntityDataSource
命名为 。 选择“ 部门 ”作为 “EntitySetName” 值,并仅选择 “DepartmentID ”和“ 名称” 属性。
从“工具箱”的“标准”选项卡中,将控件DropDownList
拖到页面,将其DepartmentsDropDownList
命名为 ,单击智能标记,然后选择“选择数据源”以启动“数据源配置向导”。
在 “选择数据源 ”步骤中,选择“ DepartmentsEntityDataSource” 作为数据源,单击“ 刷新架构”,然后选择“ 名称 ”作为要显示的数据字段,并选择 “DepartmentID ”作为值数据字段。 单击" 确定"。
使用实体框架对控件进行数据绑定的方法与其他 ASP.NET 数据源控件相同,只不过指定实体和实体属性。
切换到 “源” 视图,紧接在控件前面 DropDownList
添加“选择部门:”。
Select a department:
<asp:DropDownList ID="DropDownList1" runat="server"
DataSourceID="EntityDataSource1" DataTextField="Name"
DataValueField="DepartmentID">
</asp:DropDownList>
提醒一下,此时更改控件的EntityDataSource
标记,方法是将 和 DefaultContainerName
属性替换为 ConnectionString
属性ContextTypeName="ContosoUniversity.DAL.SchoolEntities"
。 通常最好等到创建链接到数据源控件的数据绑定控件之后再更改 EntityDataSource
控件标记,因为在进行更改后,设计器不会在数据绑定控件中为你提供 “刷新架构” 选项。
运行页面,可以从下拉列表中选择一个部门。
这将完成使用 控件的 EntityDataSource
介绍。 使用此控件通常与其他 ASP.NET 数据源控件没有区别,只不过引用实体和属性而不是表和列。 唯一的例外是想要访问导航属性。 在下一教程中,你将看到在筛选、分组和排序数据时,用于 EntityDataSource
控件的语法也可能不同于其他数据源控件。