使用 Entity Framework 4.0 和 ObjectDataSource 控件,第 1 部分:入门

作者 :Tom Dykstra

本教程系列基于由入门使用 Entity Framework 4.0 教程系列创建的 Contoso University Web 应用程序。 如果未完成前面的教程,作为本教程的起点,可以下载已创建 的应用程序 。 还可以下载完整教程系列创建 的应用程序

Contoso University 示例 Web 应用程序演示如何使用 Entity Framework 4.0 和 Visual Studio 2010 创建 ASP.NET Web Forms应用程序。 示例应用程序是虚构的 Contoso University 的网站。 它包括诸如学生入学、 课程创建和导师分配等功能。

本教程演示了 C# 中的示例。 可下载的示例包含 C# 和 Visual Basic 中的代码。

Database First

可通过三种方式处理实体框架中的数据: Database FirstModel FirstCode First。 本教程适用于 Database First。 有关这些工作流之间的差异以及如何为方案选择最佳工作流的指南的信息,请参阅 实体框架开发工作流

Web 窗体

与 入门 系列一样,本教程系列使用 ASP.NET Web Forms 模型,并假定你知道如何在 Visual Studio 中使用 ASP.NET Web Forms。 如果没有,请参阅使用 ASP.NET 4.5 Web Forms入门。 如果希望使用 ASP.NET MVC 框架,请参阅使用 ASP.NET MVC 通过实体框架入门

软件版本

教程中所示 也适用于
Windows 7 Windows 8
Visual Studio 2010 Visual Studio 2010 Express for Web。 本教程尚未使用更高版本的 Visual Studio 进行测试。 菜单选择、对话框和模板存在许多差异。
.NET 4 .NET 4.5 与 .NET 4 向后兼容,但本教程尚未使用 .NET 4.5 进行测试。
实体框架 4 本教程尚未使用更高版本的实体框架进行测试。 从 Entity Framework 5 开始,EF 默认 DbContext API 使用 EF 4.1 中引入的 。 EntityDataSource 控件旨在使用 ObjectContext API。 有关如何将 EntityDataSource 控件与 API 配合使用 DbContext 的信息,请参阅 此博客文章

问题

如果有与本教程不直接相关的问题,可以将其发布到 ASP.NET 实体框架论坛实体框架和 LINQ to Entities 论坛StackOverflow.com

控件 EntityDataSource 使你能够非常快速地创建应用程序,但它通常需要在 .aspx 页中保留大量的业务逻辑和数据访问逻辑。 如果预期应用程序的复杂性会增大,并且需要持续维护,则可以提前投入更多开发时间,以创建更易于维护的 n 层分层 应用程序结构。 若要实现此体系结构,请将表示层与业务逻辑层 (BLL) 和数据访问层 (DAL) 分开。 实现此结构的一种方法是使用 ObjectDataSource 控件而不是 EntityDataSource 控件。 使用 ObjectDataSource 控件时,实现自己的数据访问代码,然后使用具有许多与其他数据源控件相同的功能的控件在 .aspx 页面中调用它。 这使你可以将 n 层方法的优点与使用Web Forms控件进行数据访问的好处相结合。

控件 ObjectDataSource 在其他方面也提供了更大的灵活性。 由于编写自己的数据访问代码,因此可以更轻松地执行更多操作,而不仅仅是读取、插入、更新或删除特定实体类型,这些实体类型是控件旨在执行的任务 EntityDataSource 。 例如,可以在每次更新实体时执行日志记录、每当删除实体时存档数据,或者在插入具有外键值的行时根据需要自动检查和更新相关数据。

业务逻辑和存储库类

ObjectDataSource控件通过调用你创建的类来工作。 类包含用于检索和更新数据的方法,并且你向标记中的 控件提供这些方法 ObjectDataSource 的名称。 在呈现或回发处理期间, ObjectDataSource 将调用你指定的方法。

除了基本 CRUD 操作之外,创建用于 控件的 ObjectDataSource 类可能需要在读取或更新数据时 ObjectDataSource 执行业务逻辑。 例如,更新部门时,可能需要验证没有其他部门具有相同的管理员,因为一个人不能是多个部门的管理员。

ObjectDataSource在某些文档(如 ObjectDataSource 类概述)中,控件调用称为业务对象的类,其中包含业务逻辑和数据访问逻辑。 在本教程中,你将为业务逻辑和数据访问逻辑创建单独的类。 封装数据访问逻辑的类称为 存储库。 业务逻辑类包括业务逻辑方法和数据访问方法,但数据访问方法调用存储库以执行数据访问任务。

还将在 BLL 和 DAL 之间创建一个抽象层,以便于自动对 BLL 进行单元测试。 通过在业务逻辑类中实例化存储库时创建接口并使用 接口来实现此抽象层。 这样就可以为业务逻辑类提供对实现存储库接口的任何对象的引用。 对于正常操作,请提供一个适用于实体框架的存储库对象。 对于测试,请提供一个存储库对象,该对象处理存储的数据的方式易于操作,例如定义为集合的类变量。

下图显示了包含没有存储库的数据访问逻辑的业务逻辑类和使用存储库的业务逻辑类之间的差异。

Image05

首先创建网页, ObjectDataSource 其中控件直接绑定到存储库,因为它只执行基本的数据访问任务。 在下一教程中,你将使用验证逻辑创建业务逻辑类,并将 ObjectDataSource 控件绑定到该类,而不是绑定到存储库类。 还将为验证逻辑创建单元测试。 在本系列的第三个教程中,将向应用程序添加排序和筛选功能。

在本教程中创建的页面使用Departments入门 教程系列中创建的数据模型的实体集。

显示“部门”页面外观的屏幕截图。

Image02

更新数据库和数据模型

本教程将首先对数据库进行两次更改,这两项更改都需要对使用实体框架和Web Forms教程在入门中创建的数据模型进行相应的更改。 在其中一个教程中,你在设计器中手动进行了更改,以在数据库更改后将数据模型与数据库同步。 在本教程中,你将使用设计器的 “从数据库更新模型” 工具自动更新数据模型。

向数据库添加关系

在 Visual Studio 中,打开使用 Entity Framework 和 Web Forms 系列教程入门创建的 Contoso University Web 应用程序,然后打开SchoolDiagram数据库关系图。

如果查看 Department 数据库关系图中的表,将看到它有一列 Administrator 。 此列是表的 Person 外键,但在数据库中未定义外键关系。 需要创建关系并更新数据模型,以便实体框架可以自动处理此关系。

在数据库关系图中,右键单击表 Department ,然后选择“ 关系”。

Image80

在“ 外键关系 ”框中,单击“ 添加”,然后单击 “表和列规范”的省略号。

Image81

在“ 表和列 ”对话框中,将主键表和字段设置为 PersonPersonID,并将外键表和字段设置为 DepartmentAdministrator。 (执行此操作时,关系名称将从 更改为 FK_Department_DepartmentFK_Department_Person.)

Image82

在“表和列”框中单击“确定”,在“外键关系”框中单击“关闭”,然后保存更改。 如果系统询问是否要保存 PersonDepartment 表,请单击“ ”。

注意

如果删除了 Person 与列中已有 Administrator 的数据对应的行,则无法保存此更改。 在这种情况下,请使用 服务器资源管理器 中的表编辑器, Administrator 确保每 Department 一行中的值都包含表中实际存在的记录的 Person ID。

保存更改后,如果该人员是部门管理员,则无法从 Person 表中删除行。 在生产应用程序中,当数据库约束阻止删除或指定级联删除时,你将提供特定的错误消息。 有关如何指定级联删除的示例,请参阅实体框架和 ASP.NET - 入门第 2 部分

将视图添加到数据库

在将要创建的新 Departments.aspx 页中,需要提供讲师的下拉列表,其名称采用“最后一个,第一个”格式,以便用户可以选择部门管理员。 若要更轻松地执行此操作,请在数据库中创建视图。 视图将仅包含下拉列表所需的数据: (格式正确的) 全名和记录键。

“服务器资源管理器”中,展开“ School.mdf”,右键单击“ 视图 ”文件夹,然后选择“ 添加新视图”。

Image06

出现“添加表”对话框时,单击“关闭”,并将以下 SQL 语句粘贴到“SQL”窗格中:

SELECT        LastName + ',' + FirstName AS FullName, PersonID
FROM          dbo.Person
WHERE        (HireDate IS NOT NULL)

将视图另存为 vInstructorName

更新数据模型

DAL 文件夹中,打开 SchoolModel.edmx 文件,右键单击设计图面,然后选择“ 从数据库更新模型”。

Image07

“选择数据库对象 ”对话框中,选择“ 添加 ”选项卡,然后选择刚创建的视图。

Image08

单击“完成”。

在设计器中,可以看到该工具创建了一个vInstructorName实体,以及 和 Person 实体之间的Department新关联。

Image13

注意

“输出 ”和 “错误列表” 窗口中,你可能会看到一条警告消息,通知你该工具自动为新 vInstructorName 视图创建了主键。 这是预期的行为。

在代码中引用新 vInstructorName 实体时,不希望使用数据库约定,即以小写形式“v”作为前缀。 因此,将重命名模型中的实体和实体集。

打开 模型浏览器。 你会看到 vInstructorName 列为实体类型和视图。

Image14

在“ SchoolModel (不 SchoolModel.Store) ”下,右键单击“ vInstructorName ”,然后选择“ 属性”。 在 “属性” 窗口中,将 “Name ”属性更改为“InstructorName”,将 “实体集名称 ”属性更改为“InstructorNames”。

Image15

保存并关闭数据模型,然后重新生成项目。

使用存储库类和 ObjectDataSource 控件

DAL 文件夹中创建新的课堂文件,将其命名为 SchoolRepository.cs,并将现有代码替换为以下代码:

using System;
using System.Collections.Generic;
using System.Linq;
using ContosoUniversity.DAL;

namespace ContosoUniversity.DAL
{
    public class SchoolRepository : IDisposable
    {
        private SchoolEntities context = new SchoolEntities();

        public IEnumerable<Department> GetDepartments()
        {
            return context.Departments.Include("Person").ToList();
        }

        private bool disposedValue = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposedValue)
            {
                if (disposing)
                {
                    context.Dispose();
                }
            }
            this.disposedValue = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

    }
}

此代码提供返回实体集中所有实体Departments的单个GetDepartments方法。 因为你知道你将访问 Person 返回的每一行的导航属性,因此可以使用 方法指定该属性 Include 的预先加载。 类还实现 IDisposable 接口,以确保在释放 对象时释放数据库连接。

注意

常见做法是为每个实体类型创建存储库类。 在本教程中,将一个存储库类用于多个实体类型。 有关存储库模式的详细信息,请参阅 实体框架团队博客Julie Lerman 博客中的文章

方法 GetDepartments 返回 对象 IEnumerable 而不是 IQueryable 对象,以确保即使在释放存储库对象本身之后,返回的集合也可用。 每当 IQueryable 访问 对象时,该对象都可能导致数据库访问,但当数据绑定控件尝试呈现数据时,可能会释放存储库对象。 可以返回另一个集合类型,例如 IList 对象而不是 IEnumerable 对象。 但是,返回 IEnumerable 对象可确保可以执行典型的只读列表处理任务(如 foreach 循环和 LINQ 查询),但不能在集合中添加或删除项,这可能意味着此类更改将持久保存到数据库中。

创建使用 Site.Master 母版页的 Departments.aspx 页,并在名为 Content2Content控件中添加以下标记:

<h2>Departments</h2>
    <asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server" 
        TypeName="ContosoUniversity.DAL.SchoolRepository" 
        DataObjectTypeName="ContosoUniversity.DAL.Department" 
        SelectMethod="GetDepartments" >
    </asp:ObjectDataSource>
    <asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
        DataSourceID="DepartmentsObjectDataSource"  >
        <Columns>
            <asp:CommandField ShowEditButton="True" ShowDeleteButton="True"
                ItemStyle-VerticalAlign="Top">
            </asp:CommandField>
            <asp:DynamicField DataField="Name" HeaderText="Name" SortExpression="Name" ItemStyle-VerticalAlign="Top" />
            <asp:DynamicField DataField="Budget" HeaderText="Budget" SortExpression="Budget" ItemStyle-VerticalAlign="Top" />
            <asp:DynamicField DataField="StartDate" HeaderText="Start Date" ItemStyle-VerticalAlign="Top" />
            <asp:TemplateField HeaderText="Administrator" SortExpression="Person.LastName" ItemStyle-VerticalAlign="Top" >
                <ItemTemplate>
                    <asp:Label ID="AdministratorLastNameLabel" runat="server" Text='<%# Eval("Person.LastName") %>'></asp:Label>,
                    <asp:Label ID="AdministratorFirstNameLabel" runat="server" Text='<%# Eval("Person.FirstMidName") %>'></asp:Label>
                </ItemTemplate>
            </asp:TemplateField>
        </Columns>
    </asp:GridView>

此标记创建一个 ObjectDataSource 控件,该控件使用刚刚创建的存储库类,以及一个 GridView 用于显示数据的控件。 控件 GridView 指定 EditDelete 命令,但尚未添加代码来支持它们。

多个列使用 DynamicField 控件,以便可以利用自动数据格式设置和验证功能。 要使这些操作正常工作,必须在 事件处理程序中Page_Init调用 EnableDynamicData 方法。 DynamicControl (控件未在Administrator字段中使用,因为它们不适用于导航属性。)

Vertical-Align="Top"稍后将具有嵌套GridView控件的列添加到网格时,属性将变得非常重要。

打开 Departments.aspx.cs 文件并添加以下 using 语句:

using ContosoUniversity.DAL;

然后,为页面的 Init 事件添加以下处理程序:

protected void Page_Init(object sender, EventArgs e)
{
    DepartmentsGridView.EnableDynamicData(typeof(Department));
}

DAL 文件夹中,创建名为 Department.cs 的新类文件,并将现有代码替换为以下代码:

using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.DAL
{
    [MetadataType(typeof(DepartmentMetaData))]
    public partial class Department
    {
    }

    public class DepartmentMetaData
    {
        [DataType(DataType.Currency)]
        [Range(0, 1000000, ErrorMessage = "Budget must be less than $1,000,000.00")]
        public Decimal Budget { get; set; }

        [DisplayFormat(DataFormatString="{0:d}",ApplyFormatInEditMode=true)]
        public DateTime StartDate { get; set; }

    }
}

此代码将元数据添加到数据模型。 它指定 Budget 实体的 Department 属性实际表示货币,尽管其数据类型为 Decimal,并且指定值必须介于 0 和 $1,000,000.00 之间。 它还指定 StartDate 属性的格式应设置为 mm/dd/yyyy 格式的日期。

运行 Departments.aspx 页。

显示运行时“部门”页的屏幕截图。

请注意,尽管未在 Departments.aspx 页标记中为“预算”或“开始日期”列指定格式字符串,但控件已使用 Department.cs 文件中提供的元数据对其DynamicField应用了默认货币和日期格式。

添加插入和删除功能

打开 SchoolRepository.cs,添加以下代码以创建 Insert 方法和 Delete 方法。 该代码还包括一个名为 GenerateDepartmentID 的方法,该方法计算下一个可用记录键值以供 方法使用 Insert 。 这是必需的,因为数据库未配置为自动为 Department 表计算此值。

public void InsertDepartment(Department department)
{
    try
    {
        department.DepartmentID = GenerateDepartmentID();
        context.Departments.AddObject(department);
        context.SaveChanges();
    }
    catch (Exception ex)
    {
        //Include catch blocks for specific exceptions first,
        //and handle or log the error as appropriate in each.
        //Include a generic catch block like this one last.
        throw ex;
    }
}

public void DeleteDepartment(Department department)
{
    try
    {
        context.Departments.Attach(department);
        context.Departments.DeleteObject(department);
        context.SaveChanges();
    }
    catch (Exception ex)
    {
        //Include catch blocks for specific exceptions first,
        //and handle or log the error as appropriate in each.
        //Include a generic catch block like this one last.
        throw ex;
    }
}

private Int32 GenerateDepartmentID()
{
    Int32 maxDepartmentID = 0;
    var department = (from d in GetDepartments()
                      orderby d.DepartmentID descending
                      select d).FirstOrDefault();
    if (department != null)
    {
        maxDepartmentID = department.DepartmentID + 1;
    }
    return maxDepartmentID;
}

Attach 方法

方法 DeleteDepartment 调用 Attach 方法,以便重新建立对象上下文的对象状态管理器中在内存中的实体和它表示的数据库行之间维护的链接。 这必须在方法调用 SaveChanges 方法之前发生。

术语 “对象上下文 ”是指派生自用于访问实体集和实体的 ObjectContext 类的实体框架类。 在此项目的代码中, 类名为 SchoolEntities,它的实例始终名为 context。 对象上下文 的对象状态管理器 是派生自 类的 ObjectStateManager 类。 对象联系人使用对象状态管理器来存储实体对象,并跟踪每个对象是否与数据库中的对应表行同步。

读取实体时,对象上下文将其存储在对象状态管理器中,并跟踪该对象的表示形式是否与数据库同步。 例如,如果更改属性值,则会设置一个标志来指示所更改的属性不再与数据库同步。 然后,在调用 SaveChanges 方法时,对象上下文知道在数据库中要执行的操作,因为对象状态管理器确切地知道实体的当前状态与数据库状态之间的差异。

但是,此过程通常不适用于 Web 应用程序,因为读取实体的对象上下文实例及其对象状态管理器中的所有内容在呈现页面后被释放。 必须应用更改的对象上下文实例是实例化用于回发处理的新实例。 对于 DeleteDepartment 方法,控件 ObjectDataSource 会根据视图状态中的值重新创建实体的原始版本,但对象状态管理器中不存在此重新创建的 Department 实体。 如果对此重新创建的实体调用 DeleteObject 了 方法,则调用将失败,因为对象上下文不知道该实体是否与数据库同步。 但是,调用 Attach 方法会在重新创建的实体与数据库中的值之间建立相同的跟踪,而最初是在对象上下文的早期实例中读取实体时自动完成的。

有时,你不希望对象上下文跟踪对象状态管理器中的实体,你可以设置标志来阻止它这样做。 本系列后面的教程中介绍了这方面的示例。

SaveChanges 方法

这个简单的存储库类说明了如何执行 CRUD 操作的基本原则。 在此示例中,每次 SaveChanges 更新后立即调用 方法。 在生产应用程序中,你可能希望从单独的方法调用 SaveChanges 方法,以便更好地控制数据库何时更新。 (在下一篇教程结束时,你将找到一个指向白皮书的链接,该白皮书讨论工作单元模式是协调相关更新的一种方法。) 请注意,在本示例中, DeleteDepartment 方法不包含用于处理并发冲突的代码;要执行此操作的代码将在本系列的后续教程中添加。

检索插入时要选择的讲师姓名

创建新部门时,用户必须能够从下拉列表中的讲师列表中选择管理员。 因此,将以下代码添加到 SchoolRepository.cs ,以创建一个方法,以使用前面创建的视图检索讲师列表:

public IEnumerable<InstructorName> GetInstructorNames()
{
    return context.InstructorNames.OrderBy("it.FullName").ToList();
}

创建用于插入部门的页面

创建使用 Site.Master的 DepartmentsAdd.aspx 页,并在名为 Content2Content控件中添加以下标记:

<h2>Departments</h2>
    <asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server" 
        TypeName="ContosoUniversity.DAL.SchoolRepository" DataObjectTypeName="ContosoUniversity.DAL.Department"
        InsertMethod="InsertDepartment" >
    </asp:ObjectDataSource>
    <asp:DetailsView ID="DepartmentsDetailsView" runat="server" 
        DataSourceID="DepartmentsObjectDataSource" AutoGenerateRows="False"
        DefaultMode="Insert" OnItemInserting="DepartmentsDetailsView_ItemInserting">
        <Fields>
            <asp:DynamicField DataField="Name" HeaderText="Name" />
            <asp:DynamicField DataField="Budget" HeaderText="Budget" />
            <asp:DynamicField DataField="StartDate" HeaderText="Start Date" />
            <asp:TemplateField HeaderText="Administrator">
                <InsertItemTemplate>
                    <asp:ObjectDataSource ID="InstructorsObjectDataSource" runat="server" 
                        TypeName="ContosoUniversity.DAL.SchoolRepository" 
                        DataObjectTypeName="ContosoUniversity.DAL.InstructorName"
                        SelectMethod="GetInstructorNames" >
                    </asp:ObjectDataSource>
                    <asp:DropDownList ID="InstructorsDropDownList" runat="server" 
                        DataSourceID="InstructorsObjectDataSource"
                        DataTextField="FullName" DataValueField="PersonID" OnInit="DepartmentsDropDownList_Init">
                    </asp:DropDownList>
                </InsertItemTemplate>
            </asp:TemplateField>
            <asp:CommandField ShowInsertButton="True" />
        </Fields>
    </asp:DetailsView>
   <asp:ValidationSummary ID="DepartmentsValidationSummary" runat="server" 
        ShowSummary="true" DisplayMode="BulletList"  />

此标记创建两 ObjectDataSource 个控件,一个用于插入新 Department 实体,一个用于检索用于选择部门管理员的控件的讲师名称 DropDownList 。 标记创建一个 DetailsView 用于输入新部门的控件,并为控件的事件 ItemInserting 指定处理程序,以便你可以设置 Administrator 外键值。 最后是用于 ValidationSummary 显示错误消息的控件。

打开 DepartmentsAdd.aspx.cs 并添加以下 using 语句:

using ContosoUniversity.DAL;

添加以下类变量和方法:

private DropDownList administratorsDropDownList;

protected void Page_Init(object sender, EventArgs e)
{
    DepartmentsDetailsView.EnableDynamicData(typeof(Department));
}

protected void DepartmentsDropDownList_Init(object sender, EventArgs e)
{
    administratorsDropDownList = sender as DropDownList;
}

protected void DepartmentsDetailsView_ItemInserting(object sender, DetailsViewInsertEventArgs e)
{
    e.Values["Administrator"] = administratorsDropDownList.SelectedValue;
}

方法 Page_Init 启用动态数据功能。 控件事件的处理程序DropDownList保存对控件的Init引用,控件Inserting事件的处理程序DetailsView使用该引用来获取PersonID所选讲师的值并更新Administrator实体的Department外键属性。

运行页面,为新部门添加信息,然后单击“ 插入 ”链接。

Image04

输入另一个新部门的值。 在 “预算 ”字段中输入大于 1,000,000.00 的数字,然后按 Tab 键进入下一个字段。 字段中将显示一个星号,如果将鼠标指针悬停在字段中,则可以看到在该字段的元数据中输入的错误消息。

Image03

单击“ 插入”,此时会看到页面底部控件 ValidationSummary 显示的错误消息。

Image12

接下来,关闭浏览器并打开 Departments.aspx 页面。 通过向控件添加属性和DataKeyNames向控件添加属性,将删除功能添加到 DeleteMethodObjectDataSourceGridViewDepartments.aspx 页面。 这些控件的开始标记现在将类似于以下示例:

<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server" 
        TypeName="ContosoUniversity.DAL.SchoolRepository" 
        DataObjectTypeName="ContosoUniversity.DAL.Department"
        SelectMethod="GetDepartments" 
        DeleteMethod="DeleteDepartment" >

    <asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
        DataSourceID="DepartmentsObjectDataSource" DataKeyNames="DepartmentID" >

运行页面。

显示运行后的“部门”页的屏幕截图。

删除在运行 DepartmentsAdd.aspx 页时添加的部门。

添加更新功能

打开 SchoolRepository.cs 并添加以下 Update 方法:

public void UpdateDepartment(Department department, Department origDepartment)
{
    try
    {
        context.Departments.Attach(origDepartment);
        context.ApplyCurrentValues("Departments", department);
        context.SaveChanges();
    }
    catch (Exception ex)
    {
        //Include catch blocks for specific exceptions first,
        //and handle or log the error as appropriate in each.
        //Include a generic catch block like this one last.
        throw ex;
    }
}

Departments.aspx 页中单击“更新”时,ObjectDataSource控件会创建两个Department要传递给 方法的UpdateDepartment实体。 一个包含存储在视图状态中的原始值,另一个包含控件中 GridView 输入的新值。 方法中的 UpdateDepartment 代码将 Department 具有原始值的实体传递给 Attach 方法,以便在实体与数据库中的内容之间建立跟踪。 然后,代码将 Department 具有新值的实体传递给 ApplyCurrentValues 方法。 对象上下文比较旧值和新值。 如果新值与旧值不同,则对象上下文将更改属性值。 然后, 方法 SaveChanges 仅更新数据库中已更改的列。 (但是,如果此实体的更新函数映射到存储过程,则无论更改了哪些列,都将更新整行。)

打开 Departments.aspx 文件,并将以下属性添加到控件 DepartmentsObjectDataSource

  • UpdateMethod="UpdateDepartment"
  • ConflictDetection="CompareAllValues"
    这会导致旧值存储在视图状态中,以便它们可以与 方法中的 Update 新值进行比较。
  • OldValuesParameterFormatString="orig{0}"
    这会通知控件原始值参数的名称为 origDepartment

控件的开始标记 ObjectDataSource 的标记现在类似于以下示例:

<asp:ObjectDataSource ID="DepartmentsObjectDataSource" runat="server" 
        TypeName="ContosoUniversity.DAL.SchoolRepository" 
        DataObjectTypeName="ContosoUniversity.DAL.Department" 
        SelectMethod="GetDepartments" DeleteMethod="DeleteDepartment" 
        UpdateMethod="UpdateDepartment"
        ConflictDetection="CompareAllValues" 
        OldValuesParameterFormatString="orig{0}" >

OnRowUpdating="DepartmentsGridView_RowUpdating"向 控件添加属性GridView。 你将使用它根据用户在下拉列表中选择的行来设置 Administrator 属性值。 开始 GridView 标记现在类似于以下示例:

<asp:GridView ID="DepartmentsGridView" runat="server" AutoGenerateColumns="False"
        DataSourceID="DepartmentsObjectDataSource" DataKeyNames="DepartmentID"
        OnRowUpdating="DepartmentsGridView_RowUpdating">

EditItemTemplate将列的Administrator控件添加到控件,GridView紧接在该列的 ItemTemplate 控件之后:

<EditItemTemplate>
                    <asp:ObjectDataSource ID="InstructorsObjectDataSource" runat="server" DataObjectTypeName="ContosoUniversity.DAL.InstructorName"
                        SelectMethod="GetInstructorNames" TypeName="ContosoUniversity.DAL.SchoolRepository">
                    </asp:ObjectDataSource>
                    <asp:DropDownList ID="InstructorsDropDownList" runat="server" DataSourceID="InstructorsObjectDataSource"
                        SelectedValue='<%# Eval("Administrator")  %>'
                        DataTextField="FullName" DataValueField="PersonID" OnInit="DepartmentsDropDownList_Init" >
                    </asp:DropDownList>
                </EditItemTemplate>

EditItemTemplate 控件类似于 InsertItemTemplateDepartmentsAdd.aspx 页中的控件。 区别在于,控件的初始值是使用 特性设置的 SelectedValue

GridView控件之前,像在 DepartmentsAdd.aspx 页中添加控件ValidationSummary一样。

<asp:ValidationSummary ID="DepartmentsValidationSummary" runat="server" 
        ShowSummary="true" DisplayMode="BulletList"  />

打开 Departments.aspx.cs ,紧接在分部类声明之后,添加以下代码以创建私有字段以引用控件 DropDownList

private DropDownList administratorsDropDownList;

然后,为 DropDownList 控件的事件 InitGridView 控件 RowUpdating 的事件添加处理程序:

protected void DepartmentsDropDownList_Init(object sender, EventArgs e)
{
    administratorsDropDownList = sender as DropDownList;
}

protected void DepartmentsGridView_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
    e.NewValues["Administrator"] = administratorsDropDownList.SelectedValue;
}

事件的处理程序在 Init 类字段中保存对 DropDownList 控件的引用。 事件的处理程序 RowUpdating 使用 引用获取用户输入的值,并将其应用于 Administrator 实体的 Department 属性。

使用 “DepartmentsAdd.aspx ”页添加新部门,然后运行 Departments.aspx 页面并单击添加的行上的“ 编辑 ”。

注意

由于数据库中的数据无效,无法编辑未添加 (即数据库) 中已有的行;使用数据库创建的行的管理员是学生。 如果尝试编辑其中一个,则会收到一个错误页面,其中报告了类似错误 'InstructorsDropDownList' has a SelectedValue which is invalid because it does not exist in the list of items.

Image10

如果 输入的预算金额 无效,然后单击“ 更新”,则会看到在 Departments.aspx 页中看到的相同星号和错误消息。

更改字段值或选择其他管理员,然后单击“ 更新”。 将显示更改。

显示“部门”页的屏幕截图。

这将完成对使用基本 CRUD 的控件 (ObjectDataSource 使用实体框架创建、读取、更新、删除) 操作的介绍。 你已生成一个简单的 n 层应用程序,但业务逻辑层仍与数据访问层紧密耦合,这会使自动化单元测试复杂化。 在以下教程中,你将了解如何实现存储库模式,以便于单元测试。