Entity Framework 4 in Action读书笔记——第一章:数据访问重载:Entity Framework(2)

上一篇讲解了通用数据容器,这一篇使用类来组织数据。

类是面向对象编程语言的基础。使用类,你不需要知道具体的存储机制,数据源可以是数据库,Web服务,XML文件等。类提供了很多优势,尤其是在企业应用中。

1.强类型 2.编译时检查 3.易于开发 4.存储无关

使用类展示数据

我们重新从零开始。你的客户想要在表格中展示所有的订单,第一步就是要新建一个Order类容纳订单数据,如下图所示:

image

第二步再新建一个类,这个类包含一个从数据库中读取数据的方法,并将数据转换成对象。这个容器类通常放在一个单独的程序集里,就是所谓数据层。方法代码如下:

订单的集合
  1. public List<Order> GetOrders()
  2.  {
  3.      using (SqlConnection conn = new SqlConnection(connString))
  4.      {
  5.          using (SqlCommand comm = new SqlCommand("select * from orders", conn))
  6.          {
  7.              conn.Open();
  8.              using (SqlDataReader r = comm.ExecuteReader())
  9.              {
  10.                  List<Order> orders = new List<Order>();
  11.                  while (rd.Read())
  12.                  {
  13.                      orders.Add(
  14.                      new Order
  15.                      {
  16.                          CustomerCode = (string)rd["CustomerCode"],
  17.                          OrderDate = (DateTime)rd["OrderDate"],
  18.                          OrderCode = (string)rd["OrderCode"],
  19.                          ShippingAddress = (string)rd["ShippingAddress"],
  20.                          ShippingCity = (string)rd["ShippingCity"],
  21.                          ShippingZipCode = (string)rd["ShippingZipCode"],
  22.                          ShippingCountry = (string)rd["ShippingCountry"]
  23.                      }
  24.                      );
  25.                  }
  26.                  return orders;
  27.              }
  28.          }
  29.      }
  30.  }

看到这段代码,很多人的第一反应是,怎么这么多代码啊?不过没关系,当事情变得复杂的时候,类就会给我们提供更多的帮助了。

从单个类到对象模型

你已经看到怎样创建一个单独的类以及用数据库中的数据初始化它。但是它真正强大的地方是创建多个类并将它们彼此联系起来。在数据库中,Order和OrderDetail之间的关系被描述为Order表中OrderId列和OrderTable中OrderId列的外键约束。从数据库设计的角度,这是正确的方法。但是在面向对象的世界中,这是行不通的。我们创建一个OrderDetail类并给它一个OrderId属性这是没有意义的。最好的解决方案就是运用类的独特性:类的属性类型可以是用户自定义的类。也就是说,在Order类中可以引用OrderDetail对象的集合,OrderDetail可以引用Order对象。当创建了这些关系,也就开始创建对象模型了。

关系和对象的不同

了解面向对象和关系的区别很重要,因为它影响着对象模型或者领域模型和数据库的设计。

下面就从几个方面讲述它们的不匹配。

1、数据类型(datatype)

(1).当往数据库表中添加一列时,必须确定它的类型。现代数据库支持的类型有char, varchar, int, decimal, date等等。但是涉及到类就同了,数据库中的int和bigint类型可以跟.NET中的Int32和Int64类型匹配,但是数据库其他类型就没有跟.NET中类型有确切的匹配了。

(2).在数据库中可以设置约束条件,比如说给nvarchar类型的数据设置一个最大长度。但是在.NET中没有nvarchar类型,与此类型相近的就是String类型,如果想设置最大长度只能在属性中或者方法中设定然后再存储到数据库中。

(3).数据库可以接受二进制数据,但是二进制列不知道这些数据代表什么,可能是一个文本文档、PDF文件或者图片。在.NET中可以用Object表示这一列,但是没有意义,因为你明确的知道在二进制列中存储的是什么类型的数据。如果存储的是一个文件,可以用Stream属性,如果是图片,那么Images类型是你最佳选择。

(4).在SQL Server2005中有DateTime和Small-DateTime,SQL Server2008中新增了Date 和 Time两个类型。如你所想,第一个只存储日期,第二个只存储时间。在.NET中只有一个DateTime类来表示日期和时间。

2、关联(association)

谈到关联,最大的不匹配就是关系世界和对象世界中的关系是怎样维持的。

(1).一对一关系

如果往Order表中加入其他列,这起初看似是一个小的改动,不会有太大危险,但是事实并非如此,因为很多程序都要用到这个表。替代方法是新建一个表,用OrderId作为主键,在这个表中加入新增的列。数据库中这是一个合理的权衡办法,如果在对象模型中也这么做,那就没有意义了。最好的方法就是在Order类中添加一个属性。如下图所示:

image

(2).一对多关系

在数据库中表示“多”一方的表包含父表中的主键。例如,在OrderDetail表中包含一个连接到Order的OrderId列,数据库术语称为外键。从本质上讲,数据库表的关联既是单一的又是双向的。单一的是指只需在OrderDetail一方定义关系,在Order表上不需定义任何东西。双向是指即使你只修改一方,也会影响到另一方。这是可能的,因为SQL允许你在数据表间执行联合查询。

在面向对象世界中,不存在这种机制,因为每样东西都是明确声明的。OrderDetail类通过Order属性到Order的引用,这一点有点像数据库。实际的不同你也必须修改Order类,添加一个包含OrderDetail对象集合的OrderDetails属性。这种关系如下图所示:

image

(3).多对多关系

多对多关系中,表与表之间的关系没有主次之分。例如你有Product和Supplier表,你不能简单的在一个表上创建外键表示它们的关系。Product和Supplier表只包含它们自己的数据,这样一来,两个表不能直接相连,需要依靠第三个表。

在对象模型中的做法是创建Product和Supplier两个类。在Product类中添加一个包含Supplier对象集合的Suppliers属性,同样,在Supplier类中添加一个包含Product对象集合的Products属性。如图所示:

image

3、颗粒度(granularity)

granularity在这里不知道怎么翻译比较合适,暂且翻译成颗粒度吧。颗粒度问题指的是类的个数和数据库中表的个数不一致。一对一关系中,有两个表Order和Order2,但是只有一个Order类。还是让我们看例子吧。

在Order表中有一个送货地址,被分成了4列:address,city,zip code和country。如果你还要处理其他地址,比如账单地址,你还要在Order表中添加4列。如图所示:

image

在Order类中已经送货地址的四个属性,所以再添加四个也不会有问题,尽管它工作的很顺利,这些属性会使类变得越来越大,越来越难懂。更重要的是,customers和suppliers表中有地址,或者其他还有地址。类可以重用,创建一个AddressInfo类,然后重用它不是更好吗?下面是重构完的代码:

AddressInfo类和Order类
  1. public class AddressInfo
  2. {
  3.     public string Address { get; set; }
  4.     public string City { get; set; }
  5.     public string ZipCode { get; set; }
  6.     public string Country { get; set; }
  7. }
  8. public class Order
  9. {
  10.     public Address ShippingAddress { get; set; }
  11.     public Address BillingAddress { get; set; }
  12. }

4、继承(inheritance)

继承不匹配指的是在数据库中实现继承是不可能的。我们添加一个Customer类来完善对象模型,然后让Customer和Supplier类都继承自Company。在关系型数据库中你不能简单的声明Customer表继承自另一个表。你可以创建一个包含Customer和Supplier数据的表或者是单独创建。不管选择哪种方式,在数据库和对象模型都存在不匹配的问题。

在对象模型中,有一个Product类。一个商店各种各样的产品,如鞋子,衬衫,高尔夫设备,有用设备等等。这些东西都有一些共有的属性,如价格和颜色等。即使在这样一个例子中,继承也会对你有所帮助。现在创建一个Product类,然后为每一个具体的产品创建类。但是当你设计OrderDetail类的时候问题出现了,在OrderDetail类中,需要一个属性指定是哪个产品的订单详细信息。即使在运行时,这个属性的类型都是Product,具体对象的实例可能是鞋子,衬衫或者任何继承自Product类型的产品。如图所示:

image

这种类型的结构称为多态,在数据库中是不可能表示的。

5、标识(identity)

数据库表中标识一行的是主键,因此你为一个表选择主键时一定要注意。有时候你可能想使用自然键(natural key),比如产品的代码,但是这种选择有可能带来麻烦。假如你要修改产品的代码,因为你输错了。但是产品代码在OrderDetail表中是外键,所以你必须更新OrderDetail表中的产品代码。问题是你不能更新OrderDetail表中的产品代码,因为如果你修改的值在Product表中不存在就会出现错误。另一方面,你不能修改Product表中的值,因为违反了外键约束。

这看起来是最恼人的问题了,但是还有一个原因要避免使用自然键。产品代码可能是比较长的字符串,但是几乎所有的数据库都为存储和搜索整数值做了优化,所以最有效的主键是代理键(surrogate key)。代理键的使用可以允许你修改其他任何列的值而不用担心影响到其他表。

到目前为止,我们已经说明了一个对象就是表中一行的面向对象表示,主键属性将对象和行联系起来。问题是,如果比较两个相同类型的不同对象,即时它们包含了相同的数据也证明是不相同的。为什么包含相同数据的对象是不同的呢?因为默认情况下指向表示数据库表中同一行数据的不同对象的两个变量是不同的。如果两个变量指向相同的实例,那么它们是相等的,这被称之为“引用相等”。如果有一个方法可以改变这种默认行为,肯定是更可取的。如果两个不同的对象包含相同的数据,那么它们比较的结果应该返回True。在.NET中,可以重写Equals和GetHashCode方法到达同等的效果。

写在最后的话

在前面,我们看到了关系世界和对象世界之间存在这么多的不同,下一篇将就这些不同给出解决方案。

posted @ 2011-08-12 17:18  BobTian  阅读(4515)  评论(4编辑  收藏  举报