Evil 域

当Evil遇上先知

导航

VB9.0新特性之LINQ(六)——1对N

Posted on 2008-08-02 10:41  Saar  阅读(1109)  评论(0编辑  收藏  举报

 

在关系型数据库中,1:N关系往往是编程处理的关系。通过本文对LINQ映射SQL中表所形成的类的小小的探索,大家可以看到LINQ如何处理1:N关系从表到实体的映射。并通过两个实例,来看看怎样从正、反两个方向使用LINQ来获取1:N关系中的数据。

 

在关系型数据库(RDBMS)中,存在着三类[关系]与[关系]之间的{关系} -.-!!! 看不明白?嗯,把中括号里的关系替换成[表],再试试看能不能看明白J。这三种关系是:一对一(1:1),一对多(1:N)和多对多(N:M)。

 

举个例子来说,例如,一个学生有一个学号,于是,学生跟学号之间就是1:1的关系;一个班级有多个学生,但一个学生只能属于一个班级,帮班级与学生之间是1:N关系;一个学生学多门课程,每一门课程又可以被不同的学生选修,故学生与课程之间是N:M关系。

 

在关系型数据库理论中,有完备的方案来存储以上关系的数据。针对于1:1关系,可以将两个表合而为一,只需一个主键即可完成唯一行的标识;对于1:N的关系,需要两个表一表示,主表表示1端,从表设置一个外键参考主表的主键;对于N:M的关系,需要三个表来完成,两个表表示两个不同的实体,中间表用来联系两个实体表。实体表与中间表形成1:N的关系,同时,中间表取两个实体表的主键作为联合主键。

 

(上面这段文字果然是不知所云,很玄乎啊-.-,跟大学里数据库原理相关知识以及一、二、三范式有得一拼啊:-)。没关系,本篇只是将其作为一个引子,不会作展开讨论。)

 

可以看到,1:1的关系只需要当作一个实例处理,比较简单;N:M的关系,可以看成是两个1:N的关系的组合。因此,如何处理1:N的关系,是程序开发的重点,同样也是LINQ映射数据库的一个核心。

 

首先,结合本系列一贯的例子,来看看与NotebookStorage相关的1:N关系。由于单单一个实体不会产生1对多关系,我们引入一个Warehouse,把NotebookStorage里的笔记本存放到仓库里去。这就像学生与班级的关系一样,一个Warehouse可以存放多个Notebook,但一个Notebook却只能存放在一个Warehouse里。故Notebook与Warehouse的关系是:1:N。它们之间的ER图如下:

下面给出建表的数据库脚本。建库的完整的脚本,大家可以到《VB9.0新特性之LINQ(五) - 数据库生成的类》一文的收缩代码中获取,并替换建表的Code。

/*==============================================================*/

/* DBMS name: Microsoft SQL Server 2005 */

/* Created on: 2008/7/31 22:12:24 */

/*==============================================================*/

 

 

alter table NotebookStorage

drop constraint FK_NOTEBOOK_REFERENCE_WAREHOUS

go

 

if exists (select 1

from sysobjects

where id = object_id('NotebookStorage')

and type = 'U')

drop table NotebookStorage

go

 

if exists (select 1

from sysobjects

where id = object_id('Warehouse')

and type = 'U')

drop table Warehouse

go

 

/*==============================================================*/

/* Table: NotebookStorage */

/*==============================================================*/

create table NotebookStorage (

Id int identity,

Brand nvarchar(20) not null,

Type nvarchar(20) not null,

Price decimal(9,2) not null,

Weight decimal(10,1) not null,

WarehouseId int null,

constraint PK_NOTEBOOKSTORAGE primary key (Id)

)

go

 

/*==============================================================*/

/* Table: Warehouse */

/*==============================================================*/

create table Warehouse (

Id int identity,

Name nvarchar(20) not null,

Location nvarchar(255) null,

Remark ntext null,

constraint PK_WAREHOUSE primary key (Id)

)

go

 

alter table NotebookStorage

add constraint FK_NOTEBOOK_REFERENCE_WAREHOUS foreign key (WarehouseId)

references Warehouse (Id)

go

 

 

 

 

INSERT Warehouse([Name],Location)

VALUES ('WH1','Street 1');

 

INSERT Warehouse([Name],Location)

VALUES ('WH2','Street 2');

 

 

INSERT NotebookStorage (Brand, Price, [Type], Weight, WarehouseId)

VALUES ('Lenovo',16000,'T61',2.3,1);

INSERT NotebookStorage (Brand, Price, [Type], Weight, WarehouseId)

VALUES ('HP',8000,'V3742TU',2.5,1);

INSERT NotebookStorage (Brand, Price, [Type], Weight, WarehouseId)

VALUES ('HP',5399, 'HP520',2.4,2);

INSERT NotebookStorage (Brand, Price, [Type], Weight, WarehouseId)

VALUES ('DELL',8900,'D630',2.6,2);

GO

 

 

SELECT * FROM NOTEBOOKSTORAGE;

SELECT N.Id AS NotebookId, N.Brand, N.Type, N.Price, N.Weight, W.Name AS WarehouseName, W.Location

FROM NotebookStorage AS N LEFT OUTER JOIN

Warehouse AS W ON N.WarehouseId = W.Id

 

F5完成以后,大家便可以看到新表以及测试数据了:

好,打开Visual Studio,打开NbStorage.dbml,重新从数据库中把两个表拖到OR设计器上。

(不知道 NbStorage.dbml是什么东东?请参考《VB9.0新特性之LINQ(五) - 数据库生成的类》一文。)

于是,我们得到了一个新的映射关系:

呵呵,细心的朋友可能会发现,这个与前面的ER图的箭头方向是…反的。对的,是反的。在ER图中,箭头说明,NotebookStorage中的WarehouseId要参考Warehouse中的Id字段;而这里的箭头表示,Warehouse中含有0个或多个NotebookStorage对象。

 

这样,我们可以猜想一下,一共生成了几个类?根据上文的推论,我们应该有三个类被生成了。事实也正是如此。让我们来看看生成的三个类的主要签名。注,为方便查看,以下代码大量删减,切莫复制粘贴运行,一定不会成功的哦:p。

Namespace DAL

    

    <System.Data.Linq.Mapping.DatabaseAttribute(Name:="DemoDb")> _

    Partial Public Class NbStorageDataContext

        Inherits System.Data.Linq.DataContext

        

        Public ReadOnly Property NotebookStorages() As System.Data.Linq.Table(Of Entities.NotebookStorage)

            Get

                Return Me.GetTable(Of Entities.NotebookStorage)

            End Get

        End Property

        

        Public ReadOnly Property Warehouses() As System.Data.Linq.Table(Of Entities.Warehouse)

            Get

                Return Me.GetTable(Of Entities.Warehouse)

            End Get

        End Property

    End Class

End Namespace

 

Namespace Entities

    

    <Table(Name:="dbo.NotebookStorage")> _

    Partial Public Class NotebookStorage

        Implements System.ComponentModel.INotifyPropertyChanging, System.ComponentModel.INotifyPropertyChanged

        

        Private _WarehouseId As System.Nullable(Of Integer)

        

        Private _Warehouse As EntityRef(Of Warehouse)

        

        

        <Column(Storage:="_WarehouseId", DbType:="Int")> _

        Public Property WarehouseId() As System.Nullable(Of Integer)

            Get

            End Get

            Set

            End Set

        End Property

        

        <Association(Name:="Warehouse_NotebookStorage", Storage:="_Warehouse", ThisKey:="WarehouseId", IsForeignKey:=true)> _

        Public Property Warehouse() As Warehouse

            Get

            End Get

            Set

            End Set

        End Property

    End Class

    

    <Table(Name:="dbo.Warehouse")> _

    Partial Public Class Warehouse

        Implements System.ComponentModel.INotifyPropertyChanging, System.ComponentModel.INotifyPropertyChanged

        

        

        Private _NotebookStorages As EntitySet(Of NotebookStorage)

        

        <Association(Name:="Warehouse_NotebookStorage", Storage:="_NotebookStorages", OtherKey:="WarehouseId")> _

        Public Property NotebookStorages() As EntitySet(Of NotebookStorage)

            Get

            End Get

            Set

            End Set

        End Property

    End Class

End Namespace

第一个类:NbStorageDataContext仍然充当数据库角色,它中间有两个集合,分别对应NotebookStorage表和Warehouse表。

第二个类,NotebookStorage,在关系中,属于从表,也就是1:N中的N端,那么,每一个N端的对象只能对应有一个1端的对象,故它有一个Warehouse的属性,其类型是Warehouse类;

第三个类,就是主关系对应的Warehouse类,由于它是1端,微软提供了一个EntitySet(Of NotebookStorage)类型的实体集,以方便从Warehouse方向访问其下的Notebook。

 

下面,我们来走几个例子,看看这些类到底怎么个用法:

例一:列出所有Notebook,并且附上其对应的Warehouse名称。

Imports LINQToSQL.DAL

 

Module Module1

    Sub Main()

    Dim db = New NbStorageDataContext()

    Dim var = From laptop In db.NotebookStorages

 

    For Each alaptop In var

        Console.WriteLine("LaptopID:{0} LaptopBrand:{1} WhName:{2}.", alaptop.Id, alaptop.Brand, alaptop.Warehouse.Name)

 

    Next

    End Sub

End Module

这个简单,只要把所有的laptop从NbStorageDataContext中取出,然后,通过其.Warehouse.Name属性,将Warehouse的名称带出即可。

 

例二:列出所有Warehouse,以及其下所有的Laptop:

Dim whs = From awh In db.Warehouses _

          Select awh

 

For Each aWarehouse In whs

    Console.WriteLine(aWarehouse.Name)

    For Each aLaptop In aWarehouse.NotebookStorages

        Console.WriteLine(vbTab + "{0} laptop {1} sales {2}", aLaptop.Brand, aLaptop.Type, aLaptop.Price)

    Next

Next

这个也是难不倒大家。首先对每一个Warehouse作循环,以输出Warehouse信息;在针对于每一个Warehouse,再循环输出其中的Laptop的信息。

 

运行结果如图:

 

可以看出,得益于LINQ,1:N关系一旦映射好了以后,正向、反向的访问都是非常之方便的。