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

本章内容包括:
  • 介绍EDM(Entity Data Model)
  • 创建EF域模型类
  • 描述类
  • 描述数据库
  • 映射类到数据库

首先,让我们讨论一下EDM。

Entity Data Model

Entity Data Model(EDM)是EF的核心。事实上,EF就是在对象模型(Ojbect Model)和数据库之间创建一个抽象层,用来降低两者的耦合度的工具。应用程序只与对象模型类发生作用而忽略数据库的存在,正是EDM才使降耦成为可能。

你已经知道了EDM可以分为三部分:

  • 概念模式(CSDL)——描述对象模型。
    存储模式(SSDL)——描述数据库结构。
    映射模式(MSL)——映射CSDL和SSDL。

下面让我们看一下EDM在哪里吧!

在Solution Explorer面板中,你直接双击EDMX文件,Visual Studio会以设计器视图打开。我们还可以用另一种方式打开,在EDMX文件上右击,选择“Open With”,在弹出的“Open With”对话框中选择“XML(Text) Editor”,然后在点击“OK”。

1

QQ截图20111026210046

下面你就可以看到折叠的EDMX文件了:

1

EDM在edmx:Edmx/edmx:Runtime路径下。在它里面可以看到存储模式(storage schemas),概念模式(conceptual schemas)和映射模式(mapping schemas)。与设计器相关的信息存储在edmx:Edmx/edmx:Designer路径下。

EF对EDMX文件并不感兴趣,它只能理解EDMX分解成的三部分:概念模式(*.csdl),存储模式(*.ssdl),映射模式(*.msl)。

如果查看连接字符串的metadata属性会发现有对这三个文件的引用:

metadata=res://*/Model.csdl|res://*/Model.ssdl|res://*/Model.msl

EDMX文件包含EDM,但是EF并不理解它。连接字符串引用在项目中任何位置的三个映射文件:这是怎么工作的呢?答案是在编译时,设计器从EDMX文件中抽取节点,并未每一个节点创建一个单独的文件。

这些文件或者是嵌入到程序集中或者复制到输出目录。你可以通过打开设计器属性,设置“Metadata Artifact Processing”属性为“Embed in Output Assembly”或者“Copy to Output Directory”选择是嵌入到程序集还是复制到输出目录。

QQ截图20111026213412

现在你已经了解了EDM的基础知识以及如何与Visual Studio集成在一起的,是时候创建和映射Models了。创建Model,你需要做的第一件事是设计实体(Entites)。

创建实体

设计器生成代码创建实体会遵循如下步骤:

  1. 创建实体代码。
  2. 创建概念模式(conceptual schema)
  3. 创建存储模式(storage schema)
  4. 创建映射模式(mapping schema)

在下面几节,你将手动执行这些步骤,看看映射是如何工作的。

创建实体代码

在OrderIT中的订单过程包括Order和OrderDetail实体加上AddressInfo复杂类型(complex type)。暂时,我们先不考虑关联,后面再讨论。

创建订单方案模型
public class AddressInfo
{
    public virtual string Address { get; set; }
    public virtual string ZipCode { get; set; }
    public virtual string City { get; set; }
    public virtual string Country { get; set; }
}
public class Order
{
    public virtual int OrderId { get; set; }
    public virtual DateTime OrderDate { get; set; }
    public virtual AddressInfo ShippingAddress { get; set; }
    public virtual DateTime EstimatedShippingDate { get; set; }
    public virtual DateTime ActualShippingDate { get; set; }
}
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; }
}

在这里,复杂属性应该注意一下。当实例化Order时,ShippingAddress是null,因为它没有被创建。这也就意味着你每次创建一个Order就要创建一次ShippingAddress,这样会产生重复和易出错的代码。

有两个选择可以解决这个问题:1.在Order的构造函数中,实例化ShippingAddress;2.第一次访问时延迟实例化它。

实例化复杂属性的两种方式
//构造函数实例化
public Order()
{
    ShippingAddress = new AddressInfo();
}

//延迟实例化
private AddressInfo _ShippingAddress;
public virtual AddressInfo ShippingAddress
{
    get
    {
        _ShippingAddress = _ShippingAddress ?? new AddressInfo();
        return _ShippingAddress;
    }
    set
    {
        _ShippingAddress = value;
    }
}

这两种方法的结果是一样的,选择哪一个由你自己决定。

还有一个步骤我们跳过了:重写Equals和GetHashCode方法。当你创建一个模型,为每个类重写这两个方法是很重要的。

在Order实体中实现Equals和GetHashCode
public override bool Equals(object obj)
{
    Order order = obj as Order;
    if (order == null)
    {
        return false;
    }
    return order.OrderId == this.OrderId;
}

public override int GetHashCode()
{
    return OrderId.GetHashCode();
}

这段代码说明了两个Order如果它们的OrderId属性是相同的就相等。

在这一节中创建的类还不能被EF使用,你必须在EDM中放入必需的信息,完成这个工作的第一步就是概念(conceptual)文件。

在概念模式(conceptual schema)中描述实体(Entities)

概念模式(conceptual schema)包含对实体的描述。创建这个模式一点也不难,但是由于牵涉很多XML标签,刚开始你可能会感到困惑。幸运的是,这些标签在存储模式(storage schema)中被重用,大大地简化了整个EDM。

在OrderIT的EDMX文件中,概念模式(conceptual schema)的路径是edmx:Edmx/edmx:Runtime/edmx:ConceptualModels。如果你手动创建概念文件(conceptual file),可以将它命名为OrderIT.csdl并且在数据库连接字符串中引用它。在CSDL文件中,你必须忽略前面的XML路径,因为它是EDMX的一部分,这在存储和映射文件中也是一样的。

CSDL的基础结构很简单。它有一个主要元素(main element)Schema,在它里面有一个EntityContainer元素加上每一个实体的EntityType和每一个复杂类型的ConplexType。

CSDL结构

对它的结构具有广泛的理解当然是好的,但了解每个节点是掌握映射必须的。下面从最外层的Schema开始,一个一个的分析它们。

SCHEMA

Schema是非常简单的元素。它包含的特性(attribute)Namespace和Alias指定它的命名空间和别名。

      <Schema Namespace="OrderITModel" Alias="Self" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns="http://schemas.microsoft.com/ado/2008/09/edm"></Schema>

因为Schema仅仅是个容器,没有什么特殊的特性。

ENTITYCONTAINER

EntityContainer元素在EDM中声明实体集合和关系集合。在这里声明的实体集合用来生成上下文类的代码。该元素有两个特性,一个是Name,设计器设置它的值为你在EDMX向导中输入的连接字符串名称。如果你手动创建该文件,我们建议设置该特性的值为应用程序的名字加上Entities。另一个是批注特性annotation:LazyLoadingEnabled,默认情况下,annotation:LazyLoadingEnabled 值设置为true。

EntityContainer里面是EntitySet。在第二章中已经知道了在模型中不存在继承自另一个的实体集。例如Order有一个实体集,Company有一个实体集,但是Supplier和Customer没有,因为它们继承自Company。

EntitySet有两个特性:

  • Name——声明实体集唯一的名字。
  • EntityType——包含实体的完全限定名(FQN)。

最后,EntityContainer看起来向下面的清单:

在EntityContainer中定义实体集
        <EntityContainer Name="OrderITEntities" annotation:LazyLoadingEnabled="true">
          <EntitySet Name="Orders" EntityType="OrderITModel.Order" />
          <EntitySet Name="OrderDetails" EntityType="OrderITModel.OrderDetail" />
        </EntityContainer>

COMPLEXTYPE AND ENTITYTYPE

ComplexType仅有一个Name特性,它设置为类的完全限定名(FQN)。在它内部是Property节点,Property有一些特性,如下:

特性(Attribute)

描述(Description)

Required

Name 指定属性的名称 Yes
Type 指定属性的CLR类型,如果属性是复杂类型,应该设置为复杂类型的FQN Yes
Nullable 指定属性是否可为空,默认值是true。 No
FixedLength 指定该属性是否必须是固定长度 No
MaxLength 指定值的最大长度 No
Scale 指定在decimal类型中,允许逗号后面多少位 No
Precision 指定decimal类型中允许多少位数字 No
store:StoreGeneratedPattern

指定在插入更新时数据库是怎样设置列的。有三个可选值:

  • None——使用应用程序的值(默认)
  • Identity——在插入时由数据库计算,应用程序的值在更新时使用
  • Computed——在插入和更新时都由数据库计算
No
ConcurrencyMode 指定属性是否执行并发性检查。要启用检查,设置值为Fixed。 No

复杂类型准备好后,你就可以使用它创建实体了。EntityType就是创建实体的地方。它有一个必须的Name特性,用来指定类的名称。还有两个可选特性:

  • Abstract——指定该类是否是抽象的。
  • BaseType——指定基类

Abstract和BaseType在使用继承中很重要。

在EntityType内部,Property元素作用于类中的标量(scalar)和复杂(complex)类型,Key元素指定哪个属性是实体的主键。如果属性是复杂类型(complex type),你通过Type特性设置它表示的复杂类型。Key元素没有特性;它只有一个PropertyRef节点。PropertyRef只有一个Name特性,包含属性的名称。

说了这么多,肯定还是不明白,下面列出代码,对照着很容易就明白了:

描述复杂类型和实体
<ComplexType Name="AddressInfo" >
  <Property Type="String" Name="Address" Nullable="false" MaxLength="20" />
  <Property Type="String" Name="City" Nullable="false" MaxLength="20" />
  <Property Type="String" Name="Country" Nullable="false" MaxLength="20" />
  <Property Type="String" Name="ZipCode" Nullable="false" MaxLength="10" />
</ComplexType>
<EntityType Name="Order">
  <Key>
    <PropertyRef Name="OrderId" />
  </Key>
  <Property Name="OrderId" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
  <Property Name="EstimatedShippingDate" Type="DateTime" />
  <Property Name="ActualShippingDate" Type="DateTime" />
<Property Name="ShippingAddress" Type="OrderITModel.AddressInfo" Nullable="false" />
</EntityType>
<EntityType Name="OrderDetail">
  <Key>
    <PropertyRef Name="OrderDetailId" />
  </Key>
  <Property Name="OrderDetailId" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
  <Property Name="Quantity" Type="Int32" Nullable="false" />
  <Property Name="UnitPrice" Type="Decimal" Nullable="false" Precision="19" Scale="4" />
  <Property Name="Discount" Type="Decimal" Nullable="false" Precision="19" Scale="4" />
</EntityType>

你需要了解的概念模式(conceptual schema)就这些,但是这仅仅是EDM的第一部分。你还需要创建存储模式(storage schema)描述用来存储订单和它们详细信息的表。

在存储模式(storage schema)中描述数据库

存储说明文件包含数据库结构的详细信息。你可以在这里映射一些数据库对象:表,视图,存储过程和函数。在本章,只关注前两个对象;其他的在第十章讨论。

在OrderIT的EDMX文件中,存储模式在edmx:Edmx/edmx:Runtime/edmx:StorageModels路径下;但是如果你想手动创建,可以将它命名为OrderIT.ssdl并且在连接字符串中引用它。

在前边已经提到了,概念模式(conceptual schema)的元素可以在存储模式(storage schema)中重用。的确如此,但是一些节点在存储模式中有更多的特性以适应特定的数据库选项。还是从Schema节点开始吧。
SCHEMA

Schema是存储模式的根元素,它和概念模式的Schema差不多。它有两个与数据库通信所需的特性:

  • Provider——指定ADO.NET provider。
  • ProviderManifestToken——指定数据库的版本(例如:SQL Server使用2000,2005和2008)

下面的代码片段包含一个Schema元素的例子:

    <Schema Namespace="OrderITModel.Store" 
            Alias="Self" Provider="System.Data.SqlClient" 
            ProviderManifestToken="2008" 
            xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" 
            xmlns="http://schemas.microsoft.com/ado/2009/02/edm/ssdl">
      </Schema>

跟概念文件中一样,Schema有一个容器(container)节和详细(detail)节。

ENTITYCONTAINER

EntityContainer声明了稍后在文件中描述的数据库对象。它仅有的特性是Name,表示容器的名称。

设计器设置它的值为命名空间的名称加上StoreContainer。尽管你可以在概念模式里有多个EntityContainer,但是在存储模式中这是没有意义的,因为对象属于单个数据库。

在EntityContainer内,你为每个表或视图创建一个EntitySet元素。EntitySet在存储模式中比概念模式中有更多的特性:

  • Name——指定对象的逻辑名。设计器使用表的名称,但不是强制的。
  • EntityType——表示对象的完全限定名(FQN),格式为{namespace}.{name}。
  • Schema——表示表的持有者。
  • Table——表示表的物理名。Table不是强制的,当它忽略时就是用Name。
  • store:Type——指定对象是表(Tables)还是视图(Views)
定义数据库表和视图
 <EntityContainer Name="OrderITModelStoreContainer">
    <EntitySet Name="Order" EntityType="OrderITModel.Store.Order" store:Type="Tables" Schema="dbo" />
    <EntitySet Name="OrderDetail" EntityType="OrderITModel.Store.OrderDetail" store:Type="Tables" Schema="dbo" />
  </EntityContainer>

下面描述表的结构。

ENTITYTYPE

使用EntityType元素描述对象,大部分和概念文件中一样。你必须为每一个EntityContainer中的EntitySet创建一个元素。在概念文件中,是继承结构部分的类只有一个EntitySet,但是有多个EntityType。数据库本身不支持继承,所以在存储文件中不可能有一个实体集和多个表或者视图。

每个EntityType节点有一个Name特性,它的值必须匹配EntityContainer中的EntitySet的Name特性。

在EntityType元素内,你必须包含描述数据库对象的元素。你再一次的使用Property声名列,Key和PropertyRef指定谁组成主键。

SSDL中的Property和CSDL中的属性差不多。SSDL增加了一些额外的特性以适应数据库需要:

  • Collation——指定列的排序规则。
  • DefaultValue——指定列的默认值
  • Unicode——指定列是否是Unicode。
描述Order和OrderDetail数据库表
        <EntityType Name="Order">
          <Key>
            <PropertyRef Name="OrderId" />
          </Key>
          <Property Name="OrderId" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
          <Property Name="OrderDate" Type="date" Nullable="false" />
          <Property Name="CustomerId" Type="int" Nullable="false" />
          <Property Name="ShippingAddress" Type="varchar" Nullable="false" MaxLength="20" />
          <Property Name="ShippingCity" Type="varchar" Nullable="false" MaxLength="20" />
          <Property Name="ShippingZipCode" Type="varchar" Nullable="false" MaxLength="10" />
          <Property Name="ShippingCountry" Type="varchar" Nullable="false" MaxLength="20" />
          <Property Name="EstimatedShippingDate" Type="datetime" />
          <Property Name="ActualShippingDate" Type="datetime" />
          <Property Name="Version" Type="timestamp" Nullable="false" StoreGeneratedPattern="Computed" />
        </EntityType>
        <EntityType Name="OrderDetail">
          <Key>
            <PropertyRef Name="OrderDetailId" />
          </Key>
          <Property Name="OrderDetailId" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
          <Property Name="OrderId" Type="int" Nullable="false" />
          <Property Name="ProductId" Type="int" Nullable="false" />
          <Property Name="Quantity" Type="int" Nullable="false" />
          <Property Name="UnitPrice" Type="money" Nullable="false" />
          <Property Name="Discount" Type="money" Nullable="false" />
        </EntityType>

存储文件准备好了,现在只缺少将概念模式和存储模式链接起来的映射模式了。

创建映射文件(Mapping file)

映射文件起到了连接数据库和类的桥梁作用。当模型中一个类对应一个表时,映射很简单;但是一旦事情变得复杂,映射也变得复杂。

OrderIT的EDMX文件中,edmx:Edmx/edmx:Runtime/edmx:Mapping是映射节(mapping section)的路径。如果你想手动创建该文件,可以创建OrderIT.msl并在连接字符串中引用它。

映射文件不描述实体或表,而是映射它们。因此,这个文件的结构完全不同于那两个。这也就意味着你必须学习一堆新的XML标签和特性,如下图所示:

QQ截图20111027170054

最直接的不同就是根元素,这里是Mapping。

MAPPING AND ENTITYCONTAINERMAPPING

Mapping节点的声明是固定的,你不能加任何自定义的信息。它内部只允许是EntityContainerMapping。StorageEntityContainer特性标识存储模式容器(storage schema container),CdmEntityContainer标识概念模式容器(conceptual schema container)。

在映射(mapping)中定义容器(container)
        <EntityContainerMapping StorageEntityContainer="OrderITModelStoreContainer" 
                                CdmEntityContainer="OrderITEntities">     
        </EntityContainerMapping>

在实体容器连接之后,下一步就是映射表和视图到类。这是通过在EntityContainerMapping元素中嵌入EntitySetMapping元素完成的。

ENTITYSETMAPPING, ENTITYTYPEMAPPING, AND MAPPINGFRAGMENT

映射实体到表需要四个步骤。通过定义实体集和其内部的实体开始。在实体内部,你指定实体映射到的表,最后指定column/property两者的关联。

EntitySetMapping是让你说明你将映射哪个实体的节点。唯一的特性是Name,用于指定实体集的名称。在EntitySetMapping内部,为每一个实体集嵌入一个EntityTypeMapping元素。实体的类型通过TypeName特性设置,按照IsTypeOf(EntityName)格式,EntityName是实体的完全限定名(FQN)。

现在开始映射表。因为一个类可以映射一个或多个表(Order映射到Order表,但是Shirt映射到Product和Shirt表),在EntityTypeMapping元素内,为每个涉及到表指定了一个MappingFragment元素。它只有一个StoreEntitySet特性,并且它必须匹配存储模式实体集的Name特性。

定义实体集,类和映射表
          <EntitySetMapping Name="Orders"><EntityTypeMapping TypeName="OrderITModel.Order">
            <MappingFragment StoreEntitySet="Order">
          </MappingFragment></EntityTypeMapping></EntitySetMapping>
          <EntitySetMapping Name="OrderDetails"><EntityTypeMapping TypeName="OrderITModel.OrderDetail">
            <MappingFragment StoreEntitySet="OrderDetail">
          </MappingFragment></EntityTypeMapping></EntitySetMapping>

现在已经有了实体集,类,和表。可以开始属性到列的映射过程了。

SCALARPROPERTY AND COMPLEXPROPERTY

ScalarProperty元素映射类的标量属性(scalar property)到表或视图的列。它只有两个特性:

  • Name——表示属性的名称。
  • ColumnName——表示映射的列。

ComplexProperty用于映射复杂类型(complex type)。这个节点也有两个特性:

  • Name——指定属性的名称
  • TypeName——包含复杂类型的完全限定名(FQN)

ComplexProperty本身是没用的,必须将它的属性映射到列,需要嵌入ScalarProperty元素完成。

定义属性和列的映射
          <EntitySetMapping Name="Orders"><EntityTypeMapping TypeName="OrderITModel.Order"><MappingFragment StoreEntitySet="Order">
            <ScalarProperty Name="OrderId" ColumnName="OrderId" />
            <ScalarProperty Name="OrderDate" ColumnName="OrderDate" />
            <ScalarProperty Name="CustomerId" ColumnName="CustomerId" />
            <ScalarProperty Name="EstimatedShippingDate" ColumnName="EstimatedShippingDate" />
            <ScalarProperty Name="ActualShippingDate" ColumnName="ActualShippingDate" />
            <ScalarProperty Name="Version" ColumnName="Version" />
                <ComplexProperty Name="ShippingAddress">
                  <ScalarProperty Name="Country" ColumnName="ShippingCountry" />
                  <ScalarProperty Name="ZipCode" ColumnName="ShippingZipCode" />
                  <ScalarProperty Name="City" ColumnName="ShippingCity" />
                  <ScalarProperty Name="Address" ColumnName="ShippingAddress" />
                </ComplexProperty>
          </MappingFragment></EntityTypeMapping></EntitySetMapping>
          <EntitySetMapping Name="OrderDetails"><EntityTypeMapping TypeName="OrderITModel.OrderDetail"><MappingFragment StoreEntitySet="OrderDetail">
            <ScalarProperty Name="OrderDetailId" ColumnName="OrderDetailId" />
            <ScalarProperty Name="OrderId" ColumnName="OrderId" />
            <ScalarProperty Name="ProductId" ColumnName="ProductId" />
            <ScalarProperty Name="Quantity" ColumnName="Quantity" />
            <ScalarProperty Name="UnitPrice" ColumnName="UnitPrice" />
            <ScalarProperty Name="Discount" ColumnName="Discount" />
          </MappingFragment></EntityTypeMapping></EntitySetMapping>

到目前为止,我们已经讨论了实体,并且忽略了它们之间的关联,但是是实体间的关联才是模型的真正本质。映射它们并不复杂,但是仍然要接触EDM的三个模式(schema)。幸运的是,你已经了解了模式(schema),这将简单很多。

下一篇将在模型中定义关系。

posted @ 2011-10-27 18:12  BobTian  阅读(5687)  评论(2编辑  收藏  举报