写在之前的话
在深入研究实体框架的细节之前,我们先讨论从传统的DataSet方法转换到基于对象的方法实现数据访问所带来的便利,以及这两种方法不同的工作方式是怎样导致采用像Entity Framework这样的O/RM工具。
使用Dataset和DataReader作为数据容器
我们先看一个例子。假设有一个数据库,有Order和OrderDetail两个表,表结构如下图:
我们要展示出所有的订单,只需创建一个简单的页面,用下面的代码即可完成。
using (SqlConnection conn = new SqlConnection(connString)) { using (SqlDataAdapter da = new SqlDataAdapter("Select * from order", conn)) { DataTable dt = new DataTable(); da.Fill(dt); ListView1.DataSource = dt; ListView1.DataBind(); } }
完成了上面的需求,你的客户想看每个订单下面的详细信息,这时就变得有挑战性了,因为你可以选择有不同的方法实现:
1.先从Order表中查询出所有的订单,然后从OrderDetail表中为每一个订单查询详细信息。
2.对Order和OrderDetail两个表关连查询。
3.在两个不同的查询中检索出所有的订单和所有的详细信息。
不管选择哪一种方法,你的代码都由数据库的结构和检索数据的方式决定,每一次的改变都会很痛苦的。
下面的代码显示订单Id为1的详细信息。
using (SqlConnection conn = new SqlConnection(connString)) { using (SqlCommand cm = new SqlCommand("Select * from order ➥ where orderid = 1", conn)) { conn.Open(); using (SqlDataReader rd = cm.ExecuteReader()) { rd.Read(); date.Text = ((DateTime)rd["OrderDate"]).ToString(); shippingAddress.Text = rd["ShippingAddress"].ToString(); shippingCity.Text = rd["ShippingCity"].ToString(); } using (SqlDataReader rd = cm.ExecuteReader()) { details.DataSource = rd; details.DataBind(); } } }
上面访问数据的方式完全没有安全性和通用性。
1.完全可以很容易的写出不用知道表中字段名称实现上面功能的通用代码。
2.用字符串指定一个表的字段名称丢失了类型安全性,如果名称不正确,只有在运行时才会抛出异常。
3.上面列的返回值是Object类型的,需要显示转换为合适的类型。
通用数据容器的缺陷
一、强耦合
DataReader和DataTable不允许你透明地检索数据,而不影响用户界面的代码。这意味这你的应用程序和数据库结构是强耦合的,对数据库结构的任何改变都需要对你的程序有所改动。这应该是在数据访问层解决的问题,而不是在用户界面层。
很多时候,数据库都是为一个应用程序提供服务,这样数据组织起来很容易被使用。但是,有些程序是建立在已有的数据库之上的,这时候就不能对数据库做任何改动,因为还有其他程序在使用这个数据库。这种情况下,你的代码可能跟数据库耦合性更强,甚至超出你的想象。例如,订单可能存储在一个表中,送货地址存储在另一个表中。数据访问层的代码可能减少这种影响,但是问题依然存在。
当列的名称发生改变又会发生什么呢,其实在开发的过程中这种事情经常发生。结果是,你必须改变用户界面层的代码来适应这一变化。
二、松散类型
取得存储在DataReader和DataTable中列的值,你通常要使用字符串指定列。下面的代码是取得DataTable中一列的值。
object shippingAddress = orders.Rows[0]["ShippingAddress"];
变量shippingAddress是Object类型的,它可以存储任何类型的数据,你可能知道它存储了一个字符型的数据,要像使用字符型一样使用它,必须显示的进行类型转换。
string shippingAddress = (string)orders.Rows[0]["ShippingAddress"]; string shippingAddress = orders.Rows[0]["ShippingAddress"].ToString();
进行类型转换在性能和内存使用上都有一定的损失,因为从值类型转换到引用类型要进行装箱操作,反之进行拆箱操作。
DataReader比DataTable有优势,它提供了不需要显式转换访问字段的类型方法,方法的参数接受列在一行中得索引值。它还提供了一个提供列的名称返回索引的方法。但是输入的字符串错误,也会抛出异常。
string address = rd.GetString(rd.GetOrdinal("ShippingAddress")); string address = rd.GetString(rd.GetOrdinal("ShipingAdres")); //exception
三、性能问题
DataSet可能是.NET类库中最复杂的结构了。它包括一个或多个DataTable实例,每个DataTable实例又包括一系列DataRow对象,每个DataRow对象又由DataColumn对象构成的。DataTable可以由一个或多个列组成组成一个主键,还可以在某些列上声明外键,列还支持版本控制等等。尽管这些特性很多时候都是没用的,也常常被开发人员忽略,但是DataSet仍然在内部创建包含这些对象的空的集合。这对一个独立的应用程序可能是微不足道的性能损失,但是在一个多用户的环境中,有成千上万条请求,这将是不可接受的。
相比之下,DataReader可以适用在不同的场景中。DataTable从数据库中读出所有的数据放到内存中,但是很多时候你并不需要这么多的数据在内存中,你只需要一条条的从数据库中读出记录即可。另外一种情况,你经常查询数据而不用更新它,这种情况下,DataReader是最佳选择,因为它使用只读的方式检索数据。尽管DataReader提升了性能,但是它依然有类型转换带来的性能损失。
最后的话
下一篇,将讲解使用类来组织数据。