在模型中定义关系
在OrderIT模型中,有三种类型的关联:one-to-one,one-to-many和many-to-many。
One-to-one 关系
one-to-one关系的典型例子是订单详细(order detail)和订单(order)。创建这种类型的关联一点也不困难,但是涉及几个步骤:
1. 通过添加导航属性修改OrderDetail类。如果选择外键关联,还必须加上外键属性。
2. 修改CSDL将属性添加到类描述并引入关系。 3. 修改存储模式(storage schema)引入表之间的外键(如果存在)。 4. 修改映射模式(mapping schema)映射关系(只对独立关联)。下面让我们看一下每个步骤。
修改类
创建从order detail到order的引用,需要在OrderDetail类中创建一个Order属性。因为使用了外键,还必须添加一个外键属性,如下面的清单所示:
创建order detail引用order的属性
public class OrderDetail{ public virtual int OrderDetailId { get; set; } public virtual int Quantity { get; set; } public virtual decimal Price { get; set; } public virtual decimal Discount { get; set; } //导航属性 public virtual Order Order { get; set; } //外键属性 public virtual int OrderId { get; set; }}
在代码中的工作就这些。
修改概念模式(conceptual schema)
导航属性使用NavigationProperty元素添加到Name为OrderDetail的EntityType节点。Name特性映射元素为对象中属性,Relationship需要关系的完整名称,FromRole指定起点,ToRole指定结束点。外键属性使用Property。(书中有误)
在CSDL中为实体添加导航和外键属性
对实体的描述就这些。下面看一下关系描述。
在EntityContainer元素中,需要为每一个关联声明AssociationSet。它有两个特性:
- Name——关系集的名称。
- Association——指定类型。
在AssociationSet内,插入两个End节点,Role特性的值为类的名称,EntitySet的值为实体集的名称。
在EntityContainer外部,需要使用Association元素描述关系。它的Name属性链接到AssociationSet的描述,而内嵌的End元素描述关系的类型和它们的基数。在End元素中重要的就是Multiplicity特性,它有下面可选值:
1
0..1 *End允许指定删除级联行为。这与数据库无关,只是父实体标记为删除时,子实体也被标记了。
声明和定义关联
在本例中,Order是为1的一方,OrderDetail是为*的一方,也就是说映射的是one-to-many关系!one-to-many关系也隐式声明了one-to-one关系。一个订单有多个详细,但是一个详细只对应一个订单。
独立关联(independent association)在MSL中映射,但是外键关联(foreign-key association)在CSDL中映射。因为在本例中已经选择了外键关联,必须将映射信息添加到关联定义中。这个信息添加到Association元素内的ReferentialConstraint节点,在End节点后面。
定义外键关联
ReferentialConstraint仅仅是个容器元素。映射真正发生在Principal和Dependent节点,在这里面可以定义主类(master class)的主键和在从类(child class)的外键属性。
概念模式完成了,下面修改存储模式。
修改存储模式(storage schema)
在存储模式中描述关系和概念模式中类似。在实体容器(entity container)中声明关系,然后在它外边描述。
直接看代码吧。
在存储模式中定义数据库外键约束
修改映射模式(mapping schema)
映射模式并不知道外键关联。但它只需要知道外键属性是如何映射到数据库中的外键。
在本例中,将OrderDetail类的OrderId属性映射到OrderDetail表的OrderId列,代码如下:
订单和订单详细之间的one-to-one关系就完成了,下面看看如何建立两者的one-to-many关系。
One-to-many 关系
创建one-to-many关系跟创建one-to-one关系仅仅有一点不同。过程跟之前的完全相同,但是因为已经在EDM中添加了关联,你只需修改Order类并在CSDL中反应修改。
给父类(parent class)添加属性
Order类使用名为OrderDetails的集合属性引用它的详细。该属性必须是ICollection<OrderDetail>,ISet<OrderDetail>,或者任何实现了它们的类型,如List<T>或者HashSet<T>。
集合属性在范围是需要被初始化,否则会得到NullReferenceException。如何创建属性要根据你选择的集合类型。如果使用接口,则延迟实例化。如果使用的是真实类型,则可以在构造函数中也可以延迟实例化。
建议始终使用延迟实例化的方法,如下面的清单:创建引用对象集合的属性
public class Order{ private ICollection_OrderDetails; public ICollection OrderDetails { get { _OrderDetails = _OrderDetails ?? new HashSet (); return _OrderDetails; } set { _OrderDetails = value; } }}
修改概念模式(conceptual schema)
在概念模式中给Order描述添加新的属性,跟在OrderDetail中一样,添加一个NavigationProperty,修改Name特性并将Roles反过来,因为这里是从Order到OrderDetail。
就这些。关系已经在CSDL和SSDL中定义了;在MSL没有地方需要修改,因为导航属性不需要映射。
下一个需要分析的关系是many-to-many。在OrderIT中,这种关系存在于products和suppliers。
Many-to-many 关系
创建many-to-many的步骤也是一样的。首先,创建两个类的集合属性关联彼此,然后修改EDM。这种类型关系的不同之处是没有外键属性。类之间是直接的关系,所以数据库中的连接表在模型中没有与之对应的。这就意味着必须使用独立关联(independent association)。
到目前为止,已经看到了类,CSDL和SSDL的相关代码。下面快速回顾一下如何在product和supplie间建立多对多的关系:
1. Product和Supplier需要使用集合属性关联彼此。
2. 在CSDL中,需要映射类和它们的关联,不需要插入外键关联的信息。 3. 在SSDL中,需要声明和定义Product,Company和ProductSupplier表和它们的关联。 4. 在MSL中,需要映射Product和Supplier之间的关系到数据库。在这一点上,因为没有使用外键关联,必须定义如何关联类,这个工作在映射文件中完成。
修改映射模式(mapping schema)
在映射模式中,使用AssociationSetMapping元素映射数据库列到独立关联(independent association)。AssociationSetMapping元素有三个特性:
1. Name——指定概念模式中的关联名。
2. TypeName——表示概念模式中的关联集(association set)的全名。 3. StoreEntitySet——包含数据库中子表的名称。在AssociationSetMapping内部,放置两个EndProperty元素,它们的Name特性匹配概念模式中Association节点End元素的Role特性。最后,包含进一个ScalarProperty元素,指定类的属性和连接表的外键列。
映射独立(Independent)属性
结束了!many-to-many关系仅仅需要在映射模式中一个新的标签。在讨论继承之前,先来看看下面的内容。
关于关系的一些Tips
关于关系,首先要知道的是不是必须使用外键关联。例如,可以从OrderDetail中移除OrderId,使用独立关联和Order映射关联。但是使用外键关联比使用独立关联简单,尤其当你存储一个对象图时更是这样。如果可能,我们始终推荐使用外键。
另一件你需要知道的是不能同时使用外键关联和独立关联。你必须选择其中之一使用,设计器强制实施此规则,如果两个同时使用,它则会禁用一个。
如果你不想在many-to-many中使用独立关联,可以遵循LINQ to SQL方法:创建一个类,匹配连接表,然后再将类和表关联。这允许使用外键,但这是很差的设计,我们不建议这样。
下一篇将学习映射继承。