ASP.NET 也有一个 EntityDataSource 控件,可以用它自动完成绝大部分任务。EntityDataSource 适合和之前提到的实体数据模型生成器一起来生成快速开发的应用程序。和 SqlDataSource 非常相似,使用 EntityDataSource 时,不需要编写任何代码,甚至 EntityDataSource 要更进一步,不仅不需要编写 C# 代码,还可以略过编写查询和更新数据使用的 SQL 语句。因此,它是中小型应用程序或不需要特别调整以获得全部性能的应用程序的完美工具。
另一方面,对于喜欢完全控制数据库的人,它却不太受欢迎。如果 EntityDataSource 缺乏你需要的功能、性能或灵活性,则应该使用自定义的数据访问代码(可能需要 ObjectDataSource 的帮助)。
显示数据
创建一个 GridView 用于显示员工信息列表,创建一个 DetailsView 用于显示单个员工的详细信息。向网页拖放一个 EntityDataSource 控件,使用向导配置是速度最快的。
配置 EntityDataSource 的步骤:
- 选择一个实体数据模型或者配置一个数据库连接字符串
- 选择具体实体类,一般会选择所有列。然后可以修改数据绑定控件的标记来减少实现显示的列。(如果选择了部分列,本质上市使用了投影的方式把完整的 Employee 对象转换为匿名类型。它的限制是不能够更新数据或显示来自其他表的关联数据)
- 还可以配置是否支持插入、更新、删除。
DefaultContainerName="NorthwindEntities" EnableFlattening="False" EntitySetName="Employees">
配置 GridView ,刷新架构并移除不需要显示的列,勾选启用选择选项等。
再拖放一个 EntityDataSource 配置 DetailsView 。使用 EntityDataSource 的方式稍有不同。因为它不允许直接定义 SELECT 命令。配置它最简单的办法是单击 EntityDataSource ,选择 where 属性,出现下图,添加参数(EmployeeID)-> 选择数据源(Control)-> 显示高级属性(设置DbType 为 Int32)。
最后一步是输入使用我们创建的参数的表达式。EntityDataSource 用名称 it 给我传入当前的实体类,我们通过在名字前加上前缀 @ 来引用之前创建的参数。
DefaultContainerName="NorthwindEntities" EnableFlattening="False" EntitySetName="Employees"
Where="it.EmployeeID == @EmployeeID">
现在,在 GridView 中选择一个员工后,他的详细信息就会出现在 DetailsView 中:
获取关联数据
显示由 EntityDataSource 获取的数据时,并不仅仅局限于数据类的那些基本属性。还可以同时显示出关联的数据。这是一项强大的技术,因为它可以允许你使用 LINQ to Entities 导航字段。使用这项技术唯一的限制是只能够在 TemplateField 中使用它,一般的 BoundField 不支持它。
例如,假设你要显示所有员工的总区域数,你可以通过关联的 Orders 表得到这一信息,在 <Columns>中增加下面这段:
<%#Eval("Orders.Count") %>
还需要告诉 EntityDataSource 包含 Orders 表的记录,在属性窗口里设置 Include 的值为 Orders 或手工添加特性来达到这一目的:
DefaultContainerName="NorthwindEntities" EnableFlattening="False"
EntitySetName="Employees" Include="Orders">
编辑数据
最后一步就是配置 DetailsView 和 第二个 EntityDataSource 以支持更新,插入和删除操作。通过智能标签配置 EntityDataSource ,分别启用这三个选项并确保选中了所有列。它们会更新 EntityDataSource 标签:
DefaultContainerName="NorthwindEntities" EnableFlattening="False" EntitySetName="Employees"
Where="it.EmployeeID == @EmployeeID" EnableDelete="True" EnableInsert="True"
EnableUpdate="True">
选中 DetailsView,在智能标签里选中允许新增、允许删除、允许插入三个选项(数据源启用了增删改,DetailsView 才会出现这些选项)。
注意:
由于使用了两个数据源,在插入和删除记录的时候,网格没有能够很好的同步。要解决这个问题,可以把 GridView.EnableViewState 设为 false,这样每次回发它都会丢弃当前数据并重新执行绑定。
验证
为了让这一示例更具有实用性,应该考虑一下如何添加验证逻辑以捕获无效数据。
和所有验证场景一样,有很多可用的技术方案。
- 可用在数据库上设置约束。这种方式有效,但太底层了。并且要求把验证逻辑放到你可能不希望的地方(数据库),这样不太好执行或者代码不太好写。
- 用 TemplateField 和 ASP.NET 验证控件相结合。这需要不少的工作(它使 EntityDataSource 显得不太适合快速开发了),并把你的验证代码限制到一个控件上。
- 处理 DetailsView 或 EntityDataSource 绑定控件的事件。这两项技术都能使用,但它们使得验证不太自然,把验证限制到一个控件或者页面上。如果要在多个页面处理相同的数据,这也不是理想的办法。
- 使用分部类扩展实体数据模型。可以把自己的验证逻辑直接插入到数据类,确保无论应用程序如何操作,数据对象都不会产生无效数据。
每个实体类包含每个字段的两个分部方法,其中一个在获得(但不是应用)新字段值时调用,而另一个在应用新值时调用。分部方法的名字从字段名派生。例如,Employee 实体对象的 LastName 字段的分部方法名称是 OnLastNameChanging 和 OnLastNameChanged 。
假设不允许插入或更新 LastName 字段少于 3 个字符的记录,可以给 Employee 类添加如下的分部类声明,它实现 OnLastNameChanging()方法:
using System;
namespace NorthwindModel
{
public partial class Employee
{
partial void OnLastNameChanging(string value)
{
if (value.Length < 3)
{
throw new ArgumentException(string.Format(@"{0} is too short.
The last name must be three characters", value));
}
}
}
}
注意,我们使用了包括实体类的相同的命名空间。命名空间、类名、方法签名必须和它们要应用的分部方法完全匹配。我们检查参数的长度,不符合就抛出一个异常。Entity Framework 会封装这个 ArgumentException 异常并重新抛出 EntityDataSourceValidationException 异常。
当然,除非另外执行捕获步骤,否则网页不能优雅的处理这些异常。可以在数据控件里处理事件,比如在 ItemUpdated、ItemInserted 或 ItemDeleted (或 GridView 的 RowUpdated、RowInserted、RowDeleted)里处理错误。
不管采用哪种方式,DetailsViewUpdatedEventArgs.ExceptionHandled 属性都需要设置为 true,避免影响当前页面的处理。
protected void DetailsView1_ItemUpdated(object sender, DetailsViewUpdatedEventArgs e)
{
if (e.Exception!=null)
{
EntityDataSourceValidationException ve = e.Exception
as EntityDataSourceValidationException;
if (ve == null)
{
Label1.Text = "Data error.";
}
else
{
Label1.Text = ve.Message;
}
;
}
else
{
GridView1.DataBind();
}
}