自定义实体类与DataSet的比较

转自MSDN:http://www.microsoft.com/china/msdn/library/webservices/asp.net/CustEntCls.mspx?mfr=true


掌握 ASP.NET 之路:自定义实体类简介
发布日期: 5/24/2005 | 更新日期: 5/24/2005
Karl Seguin
Microsoft Corporation

摘要:有些情况下,非类型化的 DataSet 可能并非数据操作的最佳解决方案。本指南的目的就是探讨 DataSet 的一种替代解决方案,即:自定义实体与集合。(本文包含一些指向英文站点的链接。)

 

本页内容
 引言
 DataSet 存在的问题
 自定义实体类
 对象关系映射
 自定义集合
 管理关系
 高级内容
 小结

引言
ADODB.RecordSet 和常常被遗忘的 MoveNext 的时代已经过去,取而代之的是 Microsoft ADO.NET 强大而又灵活的功能。我们的新武器就是 System.Data 名称空间,它的特点是具有速度极快的 DataReader 和功能丰富的 DataSet,而且打包在一个面向对象的强大模型中。能够使用这样的工具一点都不奇怪。任何 3 层体系结构都依靠可靠的数据访问层 (DAL) 将数据层与业务层完美地连接起来。高质量的 DAL 有助于改善代码的重新使用,它是获得高性能的关键,而且是完全透明的。

随着工具的改进,我们的开发模式也发生了变化。告别 MoveNext 并不只是让我们摆脱了繁琐的语法,它还让我们认识了断开连接的数据,这种数据对我们开发应用程序的方式产生了深刻的影响。

因为我们已经熟悉了 DataReader(其行为与 RecordSet 非常类似),所以没花多长时间就进一步开发出 DataAdapter、DataSet、DataTable 和 DataView。正是在开发这些新对象的过程中不断得到磨炼的技能改变了我们的开发方式。断开连接的数据使我们可以利用新的缓存技术,从而大大提高了应用程序的性能。这些类的功能使我们能够编写出更智能、更强大的函数,同时还能减少(有时候甚至是大大减少)常见活动所需的代码数量。

有些情况下非常适合使用 DataSet,例如在设计原型、开发小型系统和支持实用程序时。但是,在企业系统中使用 DataSet 可能并不是最佳的解决方案,因为对企业系统来说,易于维护要比投入市场的时间更重要。本指南的目的就是探讨一种适合处理此类工作的 DataSet 的替代解决方案,即:自定义实体与集合。尽管还存在其他替代解决方案,但它们都无法提供相同的功能或无法获得更多的支持。我们的首要任务是了解 DataSet 的缺点,以便理解我们要解决的问题。

记住,每种解决方案都有优缺点,所以 DataSet 的缺点可能比自定义实体的缺点(我们也将进行讨论)更容易让您接受。您和您的团队必须自己决定哪个解决方案更适合您的项目。记住要考虑解决方案的总成本,包括要求改变的实质所在以及生产后所需的时间比实际开发代码的时间更长的可能性。最后请注意,我所说的 DataSet 并不是类型化的 DataSet,但它确实可以弥补非类型化的 DataSet 的一些缺点。

返回页首
DataSet 存在的问题
缺少抽象

寻找替代解决方案的第一个也是最明显的原因就是 DataSet 无法从数据库结构中提取代码。DataAdapter 可以很好地使您的代码独立于基础数据库供应商(Microsoft、Oracle、IBM 等),但不能抽象出数据库的核心组件:表、列和关系。这些核心数据库组件也是 DataSet 的核心组件。DataSet 和数据库不仅共享通用组件,不幸的是,它们还共享架构。假定有下面这样一个 Select 语句:

SELECT UserId, FirstName, LastName
FROM Users

我们知道这些值可以从 DataSet 中的 UserId、FirstName 和 LastName 这些 DataColumn 中获得。

为什么会这么复杂?让我们看一个基本的日常示例。首先我们有一个简单的 DAL 函数:

'Visual Basic .NET
Public Function GetAllUsers() As DataSet
Dim connection As New SqlConnection(CONNECTION_STRING)
Dim command As SqlCommand = New SqlCommand("GetUsers", connection)
command.CommandType = CommandType.StoredProcedure
Dim da As SqlDataAdapter = New SqlDataAdapter(command)
Try
Dim ds As DataSet = New DataSet
da.Fill(ds)
Return ds
Finally
connection.Dispose()
command.Dispose()
da.Dispose()
End Try
End Function

//C#
public DataSet GetAllUsers() {
SqlConnection connection = new SqlConnection(CONNECTION_STRING);
SqlCommand command = new SqlCommand("GetUsers", connection);
command.CommandType = CommandType.StoredProcedure;
SqlDataAdapter da = new SqlDataAdapter(command);
try {
DataSet ds = new DataSet();
da.Fill(ds);
return ds;
}finally {
connection.Dispose();
command.Dispose();
da.Dispose();
 }           
}

然后我们有一个页面,它使用重复器显示所有用户:

<HTML>
<body>
<form id="Form1" method="post" runat="server">
<asp:Repeater ID="users" Runat="server">
<ItemTemplate>
<%# DataBinder.Eval(Container.DataItem, "FirstName") %>
<br />
</ItemTemplate>
</asp:Repeater>
</form>
</body>
</HTML>
<script runat="server">
public sub page_load
users.DataSource = GetAllUsers()
users.DataBind()
end sub
</script>

正如我们所看到的那样,我们的 ASPX 页面利用 DAL 函数 GetAllUsers 作为重复器的 DataSource。如果由于某种原因(为了性能而降级、为清楚起见而进行了标准化、要求发生了变化)导致数据库架构发生变化,变化就会一直影响 ASPX,即影响使用“FirstName”列名的 Databinder.Eval 行。这将立刻在您脑海中产生一个危险信号:数据库架构的变化会一直影响到 ASPX 代码吗?听起来不太像 N 层,对吗?

如果我们所要做的只是对列进行简单的重命名,那么更改本例中的代码并不复杂。但是,如果在许多地方都使用了 GetAllUsers,更糟糕的是,如果将其作为为无数用户提供服务的 Web 服务,那又会怎么样呢?怎样才能轻松或安全地传播更改?对于这个基本示例而言,存储过程本身作为抽象层可能已经足够;但是依赖存储过程获得除最基本的保护以外的功能则可能会在以后造成更大的问题。可以将此视为一种硬编码;实质上,使用 DataSet 时,您可能需要在数据库架构(不管使用列名称还是序号位置)和应用层/业务层之间建立一个严格的连接。但愿以前的经验(或逻辑)已经让您了解到硬编码对维护工作以及将来的开发产生的影响。

DataSet 无法提供适当抽象的另一个原因是它要求开发人员必须了解基础架构。我们所说的不是基础知识,而是关于列名称、类型和关系的所有知识。去掉这个要求不仅使您的代码不像我们看到的那样容易中断,还使代码更易于编写和维护。简单地说:

Convert.ToInt32(ds.Tables[0].Rows[i]["userId"]);

不仅难于阅读,而且需要非常熟悉列名称及其类型。理想情况下,您的业务层不需要知道有关基础数据库、数据库架构或 SQL 的任何内容。如果您像上述代码字符串中那样使用 DataSet(使用 CodeBehind 并不会有任何改善),您的业务层可能会很薄。

弱类型

DataSet 属于弱类型,因此容易出错,还可能会影响您的开发工作。这意味着无论何时从 DataSet 中检索值,值都以 System.Object 的形式返回,您需要对这种值进行转换。您面临转换可能会失败的风险。不幸的是,失败不是在编译时发生,而是在运行时发生。另外,在处理弱类型的对象时,Microsoft Visual Studio.NET (VS.NET) 等工具对您的开发人员并没有太大的帮助。前面我们说过需要深入了解构架的知识,就是指这个意思。我们再来看一个非常常见的示例:

'Visual Basic.NET
Dim userId As Integer =
?      Convert.ToInt32(ds.Tables(0).Rows(0)("UserId"))
Dim userId As Integer = CInt(ds.Tables(0).Rows(0)("UserId"))
Dim userId As Integer = CInt(ds.Tables(0).Rows(0)(0))

//C#
int userId = Convert.ToInt32(ds.Tables[0].Rows[0]("UserId"));

这段代码显示了从 DataSet 中检索值的可能方法——可能您的代码中到处都需要检索值(如果不进行转换,而您使用的又是 Visual Basic .NET,您可能会使用 Option Strict Off 这样的代码,而这会给您带来更大的麻烦。)

不幸的是,这些代码中的每一行都可能会产生大量的运行时错误:

1.
 转换可能由于以下原因而失败:

• 值可能为空。
 
• 开发人员可能对基础数据类型判断有误(还是这个问题,即开发人员需要非常熟悉数据库架构)。
 
• 如果您使用序号值,谁知道位置 X 处实际上是一个什么样的列。
 
 
2.
 ds.Tables(0) 可能返回一个空引用(如果 DAL 方法或存储过程中有任何部分失败)。
 
3.
 “UserId”可能由于以下原因而是一个无效的列名称:

• 可能已经更改了名称。
 
• 可能不是由存储过程返回的。
 
• 可能包含错别字。
 
 

我们可以修改代码并以更安全的方式编写,即为 null/nothing 添加检查,为转换添加 try/catch,但这些对开发人员都没有帮助。

更糟糕的是,正如我们前面所说,这不是抽象的。这意味着,每次要从 DataSet 中检索 userId 时,您都将面临上面提到的风险,或者需要对相同的保护性步骤进行重新编程(当然,实用程序功能可能会有助于降低风险)。弱类型对象将错误从设计时或编译时(这时总能够自动检测并轻松修复错误)转移到运行时(这时的错误可能会出现在生产过程中,而且更难查明)。

非面向对象

您不能仅仅因为 DataSet 是对象,而 C# 和 Visual Basic .NET 是面向对象 (OO) 的语言就能以面向对象的方式使用 DataSet。OO 编程的“hello world”是一个典型的 Person 类,该类又是 Employee 的子类。但 DataSet 并没有使此类继承或其他大多数 OO 技术成为可能(或者至少使它们变得自然/直观)。Scott Hanselman 是类实体的坚决支持者,他做出了最好的解释:

“DataSet 是一个对象,对吗?但它并不是域对象,它不是一个‘苹果’或‘桔子’,而是一个‘DataSet’类型的对象。DataSet 是一只碗(它知道支持数据存储)。DataSet 是一个知道如何保存行和列的对象,它非常了解数据库。但是,我不希望返回碗,我希望返回域对象,例如‘苹果’。”1

DataSet 使数据之间保持一种关系,使它们更强大并且能够在关系数据库中方便地使用。不幸的是,这意味着您将失去 OO 的所有优点。

因为 DataSet 不能作为域对象,所以无法向它们添加功能。通常情况下,对象具有字段、属性和方法,它们的行为针对的是类的实例。例如,您可能会将 Promote 或 CalcuateOvertimePay 函数与 User 对象

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值