Evil 域

当Evil遇上先知

导航

ADO.NET Entity Framework 试水——映射

Posted on 2008-08-23 17:21  Saar  阅读(7693)  评论(24编辑  收藏  举报

在OR Mapping中,映射称得上是其灵魂。映射得当,概念模型中的实体可以很容易的持久化到数据库,开发周期短,易于维护;映射不得当,则可能出持久化性能低下、逻辑表达不清晰甚至概念模型到物理模型的不匹配等问题。本文将通过一个示例程序,来了解AEF(ADO.NET Entity Framework)中,如何进行物理模型到概念模型的映射,如何处理一对多、多对多关系,如何处理实体继承中的映射问题……

 

一、准备工作

  1. 将Visual Studio 2008及.NET Framework 3.5升级到SP1。点击转到升级地址
  2. 安装SQL SERVER 2005,VS 2008中自带的EXPRESS版的SQL SERVER应该也可以用。
  3. 下载并附加数据库:点击下载DemoDbV2
  4. 创建一个VB Console Application,并且取一个合适的名字(例如:AEFMapping之类的)。注意,目标Framework要设置成3.5版。

附加数据库后,大家可以顺便查看一下数据库当前的状况。偶这边的数据库当前状况如下:

图1

由于要形成多样的关系,本次采用的数据库示例似乎比一个表的那一种要复杂那么一点点^_^。一共分为5张表:

Notebook表用于存放笔记本信息;大家可以看到它有"IsForSale"列,用于表明,这个笔记本是不是用于销售的(在示例中:0代表不卖,1代表卖)。因此,届时,会将笔记本分为两类:笔记本和销售类笔记本。由于销售类笔记本也"是"笔记本,因此,它们之间就形成一个继承关系。这在呆会儿的映射中将会有所体现。

InitOS是指笔记本出厂时带有的操作系统,由于一台笔记本可能装有多个OS(比如偶自用的本本上装了Vista和Ubuntu),一个OS又会被多个笔记本所安装(偶的笔记本上有Vista,相信不少朋友的笔记本也装了Vista,Vista就被安装于多个笔记本上),因此,笔记本与OS之间的关系就是多对多关系,因此,我们引入一个叫NbOS的表,来记录它们两者之间的对应关系;

Warehouse代表仓库,一个笔记本只能存放在一个仓库中(要么放A仓,要么放B仓,总之不可能同时又放在A仓又放在B仓吧),但一个仓库可以存放多台笔记本(显然J),因此,笔记本与仓库之间就形成了一个一对多的关系;

WarehouseState是指当前仓库的状态,表明当前仓库是在用的啊,还是废弃了啊……每一个仓库只有一种状态(不会出现A仓库既可以用又不可以用的状况),而同一个状态下的仓库却可能有好多(A仓库,废弃;B仓库,废弃;C仓库,废弃……废弃!)。因此,仓库状态与仓库之间,也形成一对多的关系。

 

二、自动生成的映射

 

第一步,当然是添加实体数据模型。在项目上右击"Add->New Items->ADO.NET Entity Data Model",并且给它取一个名字,叫什么好呢~NbWh.edmx吧。

然后,设置连接啊、选择表啊……(此处省略步骤万千,如遇问题,参考《ADO.NET Entity Framework 试水——掠影》一文完成。)让我们来看一下最后自动生成的实体数据模型。

图2

大功告成……了一半。让我们来看一揣摩一下生成的这个东东有什么特点:五个表踏踏实实的生成了五个实体;清一色的一对多联系;我们说过要把笔记本分为销售和非销售的两类,这一点没有体现……看来,自动生成的这个模型还是有其局限性的。

 

三、一对多映射中,数据的映射方法

 

生成的概念模型中,最让人满意的要数表中列到实体中属性的映射以及一对多的联系了。列到属性的映射无非就是一个名称及一个数据类型的映射,这里就不多说了。让我们来看看一对多的联系。

以Warehouse和Notebook形成的一对多关系为例。点击一下Warehouse和Notebook之间的"小桥梁",并且打开映射详细信息面板,如图3。

图3

由于键属性在实体中就可以代表实体(例如,笔记本的唯一序列号可以把不同的笔记本区分出来),因此,两个实体间的联系,可以看成是两个键属性间的联系。对于这个"桥梁",就可以抽象两个实体键属性之间的联系。因此,把这两个键属性,映射到多端表中的主、外键,一对多的联系的映射就完成了。

大家可以试试手动来建立一个一对多的关系,例如把上图中的WarehouseState实体类删除并重新创建,然后,添加一个联系,选择好相应的主、从关系并且把主键属性映射到多端表的主键、外键。

我们来作一个一对多的查询:

目标:查询出所有的Warehouse的名称,并且,把Warehouse中的所有笔记本信息输出到屏幕上。

'Create object context

Using objContext As New NbWhEntities

    'Create Query

    Dim warehouseSet = From aWarehouse In objContext.Warehouse _

                       Select aWarehouse

    'Output all warehouse

    For Each aWarehoues In warehouseSet

        Console.WriteLine(aWarehoues.Name)

        'Load data from lazy bind

        aWarehoues.Notebook.Load()

        'Output all notebooks in current warehouse

        For Each aNotebook In aWarehoues.Notebook

            Console.WriteLine(vbTab + "Brand:{0} Type:{1} Weight:{2} Color:{3}", _

                                aNotebook.Brand, _

                                aNotebook.Type, _

                                aNotebook.Weight.ToString(), _

                                aNotebook.Color)

        Next

    Next

End Using

查询模式,我们在前面已经见到过很多了,这里不作具体解释了。大家需要注意的是代码中橘黄色背景的那一行。我们这里,通过一个Navigation属性,即Warehouse实体集的Notebook属性,来访问到了当前这个Warehouse实体下对应的全部的笔记本实体集。这里与LinQ to SQL有一点点细微的差别,AEF中使用 Lazy Load(懒惰的载入?)方式,因此,如果程序员不主动的去载入(Load),Navigation属性中的数据不会自动出现。大家可以试一下把橘黄色的那行代码注释掉,结果看到的,就只是三个空的仓库。

本例运行结果如下:

图4

 

四、处理多对多映射

 

在自动生成的实体图上,没有体现多对多联系。而是跟关系型数据库一样,用两个一对多来模拟。如果大家从概念模型出发,是不会出现这样的情况的。因此,对于多对多关系的映射,我们需要作一下处理。

首先,中间表"NbOS"是不存在的。现实生活中没有这么一个东西代表笔记本与操作系统中的联系。因此,我们做的第一件事,就是把这个中间表删除。

由于中间表被删除,Notebook与InitOS之间的羁绊亦被斩断了。我们需要重新将其建立起来,让他们比以前联系得更紧密。在实体模型设计器上右击选择"添加->关系",出现"Add Association"对话框,见图5,两个端(End)分别设置为"Notebook"和" InitOS"。Multiplicity均设置为Many,Navigation属性可以根据个人喜好设置。

图5

点击OK,完成联系的添加,大家看到Notebook和InitOS之间,就建立了一条"* to *"的桥梁。但是,这个桥梁目前只是一个空架子,多对多联系,需要映射到中间表上。点击Notebook和InitOS之间的联系,打开映射详细信息面板,添加一个表格:NbOS,并且把两个实体的键属性映射到中间表的组合关键字上——千万别对反喽。经过这两步,一个多对多关系的映射就完成了。

OK,让我们来试一下这个新的桥梁是否好使。

目标:

  1. 查询出所有笔记本名称、型号以及其出厂时的操作系统。
  2. 查询出所有操作系统的名称,以及安装了此操作系统的笔记本的名称、型号。

'Create object context

Using objContext As New NbWhEntities

    'Query out all notebooks

    Dim query1 = From aNotebook In objContext.Notebook _

                 Select aNotebook

    'Output all notebook information

    For Each aNotebook In query1

        Console.WriteLine("{0}({1})", aNotebook.Brand, aNotebook.Type)

        'Load data through navigation property

        aNotebook.InitOSes.Load()

        'Output the os list in curret notebook

        For Each anOs In aNotebook.InitOSes

            Console.WriteLine(vbTab + "{0} {1}", anOs.Name, anOs.Version)

        Next

    Next

    Console.WriteLine()

   

    'Query out all os

    Dim allOSes = From anOs In objContext.InitOS _

                  Select anOs

    'Output all init os

    For Each anOS In allOSes

        Console.WriteLine("{0} {1}", anOS.Name, anOS.Version)

        'Load data through navigation property

        anOS.Notebooks.Load()

        'Output the notebook list using the current OS

        For Each aNotebook In anOS.Notebooks

            Console.WriteLine(vbTab + "{0}({1})", aNotebook.Brand, aNotebook.Type)

        Next

    Next

End Using

我们首先查询出每一台笔记本下存在的多个操作系统;然后查询出了每个操作系统下的多个笔记本。这样子,一个多对多的映射就完成了。

输出结果下图:

图6

 

 

五、继承

 

大家注意到,在Notebook实体类中,描述了笔记本的属性:品牌、型号、重量、颜色、售价、代理商、是否出售用笔记本。在这里,后面这三个属性,可以说是专为出售笔记本而设计的。因此,我们可以把笔记本分为两类:出售用的和非出售用的。对于非出售用的笔记本,售价、代理商对其没有意义。因此,我们希望让出售用笔记本继承自普通笔记本,并且拥有这三个出售相关的属性。

那么,让我们一看看这种继承关系,在AEF中如何体现。

第一步,删除Notebook实体类中的售价、代理商、是否出售用三个属性。

第二步,右击实体模型设计器,添加一个新的实体。填写上实体的名称,例如SalesNotebook,选择基类(Base type)为Notebook。如下图:

图7

完成以后,就可以看到一在Notebook下面拖了一个大大的"尾巴"——它的子类SalesNotebook。只不过,这个子类没有任何属性。属性映射也不难,点击实体以后,在映射详细信息面板中,先添加一个表Notebook,然后,选择映射三个属性即可。完成以后,我们在实体设计器里将下将看到类似下图的效果:

图8

此时,大家试一下Ctrl+Shift+B编译一下项目,却发现有问题,出现了两个错误。呵呵,这是因为,在当前的映射中,同一个关系表Notebook对应到了两个类:实体类Notebook和SalesNotebook。于是AEF就困惑了:到底什么样的行映射到Notebook的实体,什么样的行映射到SalesNotebook的实体呢?在数据库里,我们怎么区分一个行是用于销售的笔记本还是非销售的笔记本?是通过IsForSales属性。因此,IsForSales不应该是SalesNotebook或者Notebook实体类的属性,因为两个类本身就可以将其区分。

大家注意到映射详细信息中,出现了一个绿绿的图标,叫做Add a Condition。它就是用来告诉AEF,哪些行对应到父类,哪些行对应到子类。我们把SalesNotebook中的IsForSale属性删除,然后,分别添加父、子类的条件:IsForSales=0和IsForSales=1。重新编译,成功啦。这时大家看到的实体设计器中的情形大致如下:

图9

这样,我们就可以来看看这两个类怎么来使用了。

目标:

  1. 查询出所有笔记本(销售或非销售用途的,只要是个笔记本就输出来);
  2. 查询出所有非用于销售的笔记本。

'Create object context

Using objContext As New NbWhEntities

    'Create query to select all notebooks (both for sales or not for sales)

    Dim query = From aNotebook In objContext.Notebook.OfType(Of Notebook)() _

                Select aNotebook

    'Output the results:

    For Each aNotebook In query

        Console.WriteLine("Brand:{0} Type:{1} Weight:{2}", _

                            aNotebook.Brand, aNotebook.Type, aNotebook.Weight.ToString())

    Next

 

    Console.WriteLine("Notebooks that for sales")

    'Create query to select all notebooks that for sales

    Dim query2 = From aNotebook In objContext.Notebook.OfType(Of SalesNotebook)() _

                 Select aNotebook

 

    For Each aSalesNotebook In query2

        Console.WriteLine("Brand:{0} Type:{1} Weight:{2}", _

                    aSalesNotebook.Brand, aSalesNotebook.Type, aSalesNotebook.Weight.ToString())

    Next

End Using

这里,我们使用了LinQ for Entity的From In子句中的OfType属性,指定了查询结果的类型。因此,让我们使用OfType(Of SalesNotebook)的时候,我们就找到了全部用于销售的笔记本的实体。运行结果如下:

图10

 

六、小结

本文,我们主要讨论了从关系模型到概念模型之间的映射。可以看到,从数据库生成概念模型时,AEF对一对多关系的映射支持得比较好;对于多对多关系,需要删除中间表映射到的实体并重新创建它们之间的多对多联系;我们还谈到了概念中的继承以及其与数据库中表的映射关系。

 

七、示例代码下载

点击下载

 

===返回索引===