Entity Framework 4 in Action读书笔记——第五章:域模型映射(Domain model mapping)(二)

在模型中定义关系

在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中为实体添加导航和外键属性
        <EntityType Name="OrderDetail">
          <Property Name="OrderId" Type="Int32" Nullable="false" />
          <NavigationProperty Name="Order" Relationship="OrderITModel.FK_OrderDetail_Order" FromRole="OrderDetail" ToRole="Order" />
        </EntityType>

对实体的描述就这些。下面看一下关系描述。

在EntityContainer元素中,需要为每一个关联声明AssociationSet。它有两个特性:

  1. Name——关系集的名称。
  2. Association——指定类型。

在AssociationSet内,插入两个End节点,Role特性的值为类的名称,EntitySet的值为实体集的名称。

在EntityContainer外部,需要使用Association元素描述关系。它的Name属性链接到AssociationSet的描述,而内嵌的End元素描述关系的类型和它们的基数。在End元素中重要的就是Multiplicity特性,它有下面可选值:

1
0..1
*

End允许指定删除级联行为。这与数据库无关,只是父实体标记为删除时,子实体也被标记了。

声明和定义关联
        <EntityContainer Name="OrderITEntities" annotation:LazyLoadingEnabled="true">
          <AssociationSet Name="FK_OrderDetail_Order" Association="OrderITModel.FK_OrderDetail_Order">
            <End Role="Order" EntitySet="Orders" />
            <End Role="OrderDetail" EntitySet="OrderDetails" />
          </AssociationSet>
        </EntityContainer>
        <Association Name="FK_OrderDetail_Order">
          <End Role="Order" Type="OrderITModel.Order" Multiplicity="1">
            <OnDelete Action="Cascade" />
          </End>
          <End Role="OrderDetail" Type="OrderITModel.OrderDetail" Multiplicity="*" />
        </Association>

在本例中,Order是为1的一方,OrderDetail是为*的一方,也就是说映射的是one-to-many关系!one-to-many关系也隐式声明了one-to-one关系。一个订单有多个详细,但是一个详细只对应一个订单。

独立关联(independent association)在MSL中映射,但是外键关联(foreign-key association)在CSDL中映射。因为在本例中已经选择了外键关联,必须将映射信息添加到关联定义中。这个信息添加到Association元素内的ReferentialConstraint节点,在End节点后面。

定义外键关联
        <Association Name="FK_OrderDetail_Order">
          <End Role="Order" Type="OrderITModel.Order" Multiplicity="1">
            <OnDelete Action="Cascade" />
          </End>
          <End Role="OrderDetail" Type="OrderITModel.OrderDetail" Multiplicity="*" />
          <ReferentialConstraint>
            <Principal Role="Order">
              <PropertyRef Name="OrderId" />
            </Principal>
            <Dependent Role="OrderDetail">
              <PropertyRef Name="OrderId" />
            </Dependent>
          </ReferentialConstraint>
        </Association>

ReferentialConstraint仅仅是个容器元素。映射真正发生在Principal和Dependent节点,在这里面可以定义主类(master class)的主键和在从类(child class)的外键属性。

概念模式完成了,下面修改存储模式。

修改存储模式(storage schema)

在存储模式中描述关系和概念模式中类似。在实体容器(entity container)中声明关系,然后在它外边描述。

直接看代码吧。

在存储模式中定义数据库外键约束
     <EntityContainer Name="OrderITModelStoreContainer">
       <AssociationSet Name="FK_OrderDetail_Order" Association="OrderITModel.Store.FK_OrderDetail_Order">
            <End Role="Order" EntitySet="Order" />
            <End Role="OrderDetail" EntitySet="OrderDetail" />
       </AssociationSet>
     </EntityContainer>
     <Association Name="FK_OrderDetail_Order">
       <End Role="Order" Type="OrderITModel.Store.Order" Multiplicity="1">
          <OnDelete Action="Cascade" />
       </End>
       <End Role="OrderDetail" Type="OrderITModel.Store.OrderDetail" Multiplicity="*" />
       <ReferentialConstraint>
          <Principal Role="Order">
            <PropertyRef Name="OrderId" />
          </Principal>
       <Dependent Role="OrderDetail">
            <PropertyRef Name="OrderId" />
       </Dependent>
      </ReferentialConstraint>
    </Association>

修改映射模式(mapping schema)

映射模式并不知道外键关联。但它只需要知道外键属性是如何映射到数据库中的外键

在本例中,将OrderDetail类的OrderId属性映射到OrderDetail表的OrderId列,代码如下:

          <EntitySetMapping Name="OrderDetails">
            <EntityTypeMapping TypeName="OrderITModel.OrderDetail">
               <MappingFragment StoreEntitySet="OrderDetail">
                  <ScalarProperty Name="OrderId" ColumnName="OrderId" />
               </MappingFragment>
            </EntityTypeMapping>
          </EntitySetMapping>

订单和订单详细之间的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<OrderDetail> _OrderDetails;
    public ICollection<OrderDetail> OrderDetails
    {
        get
        {
            _OrderDetails = _OrderDetails ?? new HashSet<OrderDetail>();
            return _OrderDetails;
        }
        set
        {
            _OrderDetails = value;
        }
    }
}

修改概念模式(conceptual schema)

在概念模式中给Order描述添加新的属性,跟在OrderDetail中一样,添加一个NavigationProperty,修改Name特性并将Roles反过来,因为这里是从Order到OrderDetail。

 <NavigationProperty Name="OrderDetails" Relationship="OrderITModel.FK_OrderDetail_Order" FromRole="Order" ToRole="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)属性
          <AssociationSetMapping Name="ProductSupplier" TypeName="OrderITModel.ProductSupplier" StoreEntitySet="ProductSupplier">
            <EndProperty Name="Supplier">
              <ScalarProperty Name="CompanyId" ColumnName="SupplierId" />
            </EndProperty>
            <EndProperty Name="Product">
              <ScalarProperty Name="ProductId" ColumnName="ProductId" />
            </EndProperty>
          </AssociationSetMapping>

结束了!many-to-many关系仅仅需要在映射模式中一个新的标签。在讨论继承之前,先来看看下面的内容。

关于关系的一些Tips

关于关系,首先要知道的是不是必须使用外键关联。例如,可以从OrderDetail中移除OrderId,使用独立关联和Order映射关联。但是使用外键关联比使用独立关联简单,尤其当你存储一个对象图时更是这样。如果可能,我们始终推荐使用外键。

另一件你需要知道的是不能同时使用外键关联和独立关联。你必须选择其中之一使用,设计器强制实施此规则,如果两个同时使用,它则会禁用一个。

如果你不想在many-to-many中使用独立关联,可以遵循LINQ to SQL方法:创建一个类,匹配连接表,然后再将类和表关联。这允许使用外键,但这是很差的设计,我们不建议这样。

下一篇将学习映射继承。

posted @ 2011-10-28 14:58  BobTian  阅读(2560)  评论(1编辑  收藏  举报